diff --git a/cli/app/app.go b/cli/app/app.go index c62b5e42d..aaee0d0e4 100644 --- a/cli/app/app.go +++ b/cli/app/app.go @@ -12,7 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/vm" "github.com/nspcc-dev/neo-go/cli/wallet" "github.com/nspcc-dev/neo-go/pkg/config" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func versionPrinter(c *cli.Context) { diff --git a/cli/cmdargs/parser.go b/cli/cmdargs/parser.go index cb46971dd..12cf6b180 100644 --- a/cli/cmdargs/parser.go +++ b/cli/cmdargs/parser.go @@ -12,7 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) const ( @@ -138,16 +138,16 @@ const ( // GetSignersFromContext returns signers parsed from context args starting // from the specified offset. -func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, *cli.ExitError) { +func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, cli.ExitCoder) { args := ctx.Args() var ( signers []transaction.Signer err error ) - if args.Present() && len(args) > offset { - signers, err = ParseSigners(args[offset:]) + if args.Present() && args.Len() > offset { + signers, err = ParseSigners(args.Slice()[offset:]) if err != nil { - return nil, cli.NewExitError(err, 1) + return nil, cli.Exit(err, 1) } } return signers, nil @@ -230,7 +230,7 @@ func parseCosigner(c string) (transaction.Signer, error) { } // GetDataFromContext returns data parameter from context args. -func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) { +func GetDataFromContext(ctx *cli.Context) (int, any, cli.ExitCoder) { var ( data any offset int @@ -239,17 +239,17 @@ func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) { ) args := ctx.Args() if args.Present() { - offset, params, err = ParseParams(args, true) + offset, params, err = ParseParams(args.Slice(), true) if err != nil { - return offset, nil, cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) + return offset, nil, cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) } if len(params) > 1 { - return offset, nil, cli.NewExitError("'data' should be represented as a single parameter", 1) + return offset, nil, cli.Exit("'data' should be represented as a single parameter", 1) } if len(params) != 0 { data, err = smartcontract.ExpandParameterToEmitable(params[0]) if err != nil { - return offset, nil, cli.NewExitError(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1) + return offset, nil, cli.Exit(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1) } } } @@ -258,9 +258,9 @@ func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) { // EnsureNone returns an error if there are any positional arguments present. // It can be used to check for them in commands that don't accept arguments. -func EnsureNone(ctx *cli.Context) *cli.ExitError { +func EnsureNone(ctx *cli.Context) cli.ExitCoder { if ctx.Args().Present() { - return cli.NewExitError("additional arguments given while this command expects none", 1) + return cli.Exit(fmt.Errorf("additional arguments given while this command expects none"), 1) } return nil } @@ -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/flags/address.go b/cli/flags/address.go index f20e9948f..dc62cf088 100644 --- a/cli/flags/address.go +++ b/cli/flags/address.go @@ -7,7 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // Address is a wrapper for a Uint160 with flag.Value methods. @@ -16,11 +16,15 @@ type Address struct { Value util.Uint160 } -// AddressFlag is a flag with type string. +// AddressFlag is a flag with type Uint160. type AddressFlag struct { - Name string - Usage string - Value Address + Name string + Usage string + Value Address + Aliases []string + Required bool + Hidden bool + Action func(*cli.Context, string) error } var ( @@ -37,7 +41,7 @@ func (a Address) String() string { func (a *Address) Set(s string) error { addr, err := ParseAddress(s) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } a.IsSet = true a.Value = addr @@ -63,9 +67,9 @@ func (f AddressFlag) IsSet() bool { // (for usage defaults). func (f AddressFlag) String() string { var names []string - eachName(f.Name, func(name string) { + for _, name := range f.Names() { names = append(names, getNameHelp(name)) - }) + } return strings.Join(names, ", ") + "\t" + f.Usage } @@ -77,17 +81,57 @@ func getNameHelp(name string) string { return fmt.Sprintf("--%s value", name) } -// GetName returns the name of the flag. -func (f AddressFlag) GetName() string { - return f.Name +// Names returns the names of the flag. +func (f AddressFlag) Names() []string { + return cli.FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether the flag is required. +func (f AddressFlag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false. +func (f AddressFlag) IsVisible() bool { + return !f.Hidden +} + +// TakesValue returns true of the flag takes a value, otherwise false. +func (f AddressFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag. +func (f AddressFlag) GetUsage() string { + return f.Usage } // Apply populates the flag given the flag set and environment. // Ignores errors. -func (f AddressFlag) Apply(set *flag.FlagSet) { - eachName(f.Name, func(name string) { +func (f AddressFlag) Apply(set *flag.FlagSet) error { + for _, name := range f.Names() { set.Var(&f.Value, name, f.Usage) - }) + } + return nil +} + +// RunAction executes flag action if set. +func (f AddressFlag) RunAction(c *cli.Context) error { + if f.Action != nil { + return f.Action(c, address.Uint160ToString(f.Value.Value)) + } + return nil +} + +// GetValue returns the flags value as string representation. +func (f AddressFlag) GetValue() string { + return address.Uint160ToString(f.Value.Value) +} + +// Get returns the flag’s value in the given Context. +func (f AddressFlag) Get(ctx *cli.Context) Address { + adr := ctx.Generic(f.Name).(*Address) + return *adr } // ParseAddress parses a Uint160 from either an LE string or an address. diff --git a/cli/flags/address_test.go b/cli/flags/address_test.go index 1409720b9..349c74df2 100644 --- a/cli/flags/address_test.go +++ b/cli/flags/address_test.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" ) func TestParseAddress(t *testing.T) { @@ -109,22 +110,102 @@ func TestAddress_getNameHelp(t *testing.T) { require.Equal(t, "--flag value", getNameHelp("flag")) } -func TestAddressFlag_GetName(t *testing.T) { +func TestAddressFlag_Names(t *testing.T) { flag := AddressFlag{ - Name: "my flag", + Name: "flag", + Aliases: []string{"my"}, } - require.Equal(t, "my flag", flag.GetName()) + require.Equal(t, []string{"flag", "my"}, flag.Names()) } func TestAddress(t *testing.T) { f := flag.NewFlagSet("", flag.ContinueOnError) f.SetOutput(io.Discard) // don't pollute test output - addr := AddressFlag{Name: "addr, a"} - addr.Apply(f) + addr := AddressFlag{Name: "addr", Aliases: []string{"a"}} + err := addr.Apply(f) + require.NoError(t, err) require.NoError(t, f.Parse([]string{"--addr", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"})) require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String()) require.NoError(t, f.Parse([]string{"-a", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"})) require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String()) require.Error(t, f.Parse([]string{"--addr", "kek"})) } + +func TestAddressFlag_IsRequired(t *testing.T) { + flag := AddressFlag{Required: true} + require.True(t, flag.IsRequired()) + + flag.Required = false + require.False(t, flag.IsRequired()) +} + +func TestAddressFlag_IsVisible(t *testing.T) { + flag := AddressFlag{Hidden: false} + require.True(t, flag.IsVisible()) + + flag.Hidden = true + require.False(t, flag.IsVisible()) +} + +func TestAddressFlag_TakesValue(t *testing.T) { + flag := AddressFlag{} + require.True(t, flag.TakesValue()) +} + +func TestAddressFlag_GetUsage(t *testing.T) { + flag := AddressFlag{Usage: "Specify the address"} + require.Equal(t, "Specify the address", flag.GetUsage()) +} + +func TestAddressFlag_GetValue(t *testing.T) { + addrValue := util.Uint160{1, 2, 3} + flag := AddressFlag{Value: Address{IsSet: true, Value: addrValue}} + expectedStr := address.Uint160ToString(addrValue) + require.Equal(t, expectedStr, flag.GetValue()) +} + +func TestAddressFlag_Get(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + + flag := AddressFlag{ + Name: "testAddress", + Value: Address{Value: util.Uint160{1, 2, 3}, IsSet: false}, + } + + set.Var(&flag.Value, "testAddress", "test usage") + require.NoError(t, set.Set("testAddress", address.Uint160ToString(util.Uint160{3, 2, 1}))) + + expected := flag.Get(ctx) + require.True(t, expected.IsSet) + require.Equal(t, util.Uint160{3, 2, 1}, expected.Value) +} + +func TestAddressFlag_RunAction(t *testing.T) { + called := false + action := func(ctx *cli.Context, s string) error { + called = true + require.Equal(t, address.Uint160ToString(util.Uint160{1, 2, 3}), s) + return nil + } + + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + + flag := AddressFlag{ + Action: action, + Value: Address{IsSet: true, Value: util.Uint160{4, 5, 6}}, + } + + expected := address.Uint160ToString(util.Uint160{1, 2, 3}) + set.Var(&flag.Value, "testAddress", "test usage") + require.NoError(t, set.Set("testAddress", expected)) + require.Equal(t, expected, flag.GetValue()) + + err := flag.RunAction(ctx) + require.NoError(t, err) + require.True(t, called) +} diff --git a/cli/flags/fixed8.go b/cli/flags/fixed8.go index fcb07dea5..7b7fd3899 100644 --- a/cli/flags/fixed8.go +++ b/cli/flags/fixed8.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // Fixed8 is a wrapper for a Uint160 with flag.Value methods. @@ -15,9 +15,13 @@ type Fixed8 struct { // Fixed8Flag is a flag with type string. type Fixed8Flag struct { - Name string - Usage string - Value Fixed8 + Name string + Usage string + Value Fixed8 + Aliases []string + Required bool + Hidden bool + Action func(*cli.Context, string) error } var ( @@ -34,7 +38,7 @@ func (a Fixed8) String() string { func (a *Fixed8) Set(s string) error { f, err := fixedn.Fixed8FromString(s) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } a.Value = f return nil @@ -45,31 +49,75 @@ func (a *Fixed8) Fixed8() fixedn.Fixed8 { return a.Value } +// IsSet checks if flag was set to a non-default value. +func (f Fixed8Flag) IsSet() bool { + return f.Value.Value != 0 +} + // String returns a readable representation of this value // (for usage defaults). func (f Fixed8Flag) String() string { var names []string - eachName(f.Name, func(name string) { + for _, name := range f.Names() { names = append(names, getNameHelp(name)) - }) - + } return strings.Join(names, ", ") + "\t" + f.Usage } -// GetName returns the name of the flag. -func (f Fixed8Flag) GetName() string { - return f.Name +// Names returns the names of the flag. +func (f Fixed8Flag) Names() []string { + return cli.FlagNames(f.Name, f.Aliases) +} + +// IsRequired returns whether the flag is required. +func (f Fixed8Flag) IsRequired() bool { + return f.Required +} + +// IsVisible returns true if the flag is not hidden, otherwise false. +func (f Fixed8Flag) IsVisible() bool { + return !f.Hidden +} + +// TakesValue returns true if the flag takes a value, otherwise false. +func (f Fixed8Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag. +func (f Fixed8Flag) GetUsage() string { + return f.Usage } // Apply populates the flag given the flag set and environment. // Ignores errors. -func (f Fixed8Flag) Apply(set *flag.FlagSet) { - eachName(f.Name, func(name string) { +func (f Fixed8Flag) Apply(set *flag.FlagSet) error { + for _, name := range f.Names() { set.Var(&f.Value, name, f.Usage) - }) + } + return nil } // Fixed8FromContext returns a parsed util.Fixed8 value provided flag name. func Fixed8FromContext(ctx *cli.Context, name string) fixedn.Fixed8 { return ctx.Generic(name).(*Fixed8).Value } + +// RunAction executes flag action if set. +func (f Fixed8Flag) RunAction(c *cli.Context) error { + if f.Action != nil { + return f.Action(c, f.Value.Value.String()) + } + return nil +} + +// GetValue returns the flags value as string representation. +func (f Fixed8Flag) GetValue() string { + return f.Value.Value.String() +} + +// Get returns the flag’s value in the given Context. +func (f Fixed8Flag) Get(ctx *cli.Context) Fixed8 { + adr := ctx.Generic(f.Name).(*Fixed8) + return *adr +} diff --git a/cli/flags/fixed8_test.go b/cli/flags/fixed8_test.go index 1630696ef..d42a82b38 100644 --- a/cli/flags/fixed8_test.go +++ b/cli/flags/fixed8_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/stretchr/testify/require" + "github.com/urfave/cli/v2" ) func TestFixed8_String(t *testing.T) { @@ -45,22 +46,83 @@ func TestFixed8Flag_String(t *testing.T) { require.Equal(t, "--myFlag value\tGas amount", flag.String()) } -func TestFixed8Flag_GetName(t *testing.T) { +func TestFixed8Flag_Names(t *testing.T) { flag := Fixed8Flag{ Name: "myFlag", } - require.Equal(t, "myFlag", flag.GetName()) + require.Equal(t, []string{"myFlag"}, flag.Names()) } func TestFixed8(t *testing.T) { f := flag.NewFlagSet("", flag.ContinueOnError) f.SetOutput(io.Discard) // don't pollute test output - gas := Fixed8Flag{Name: "gas, g"} - gas.Apply(f) + gas := Fixed8Flag{Name: "gas", Aliases: []string{"g"}, Usage: "Gas amount", Value: Fixed8{Value: 0}, Required: true, Hidden: false, Action: nil} + err := gas.Apply(f) + require.NoError(t, err) require.NoError(t, f.Parse([]string{"--gas", "0.123"})) require.Equal(t, "0.123", f.Lookup("g").Value.String()) require.NoError(t, f.Parse([]string{"-g", "0.456"})) require.Equal(t, "0.456", f.Lookup("g").Value.String()) require.Error(t, f.Parse([]string{"--gas", "kek"})) } + +func TestFixed8Flag_Get(t *testing.T) { + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + flag := Fixed8Flag{ + Name: "testFlag", + } + fixedFlag := Fixed8{Value: fixedn.Fixed8(123)} + set.Var(&fixedFlag, "testFlag", "test usage") + require.NoError(t, set.Set("testFlag", "0.00000321")) + expected := flag.Get(ctx) + require.Equal(t, fixedn.Fixed8(321), expected.Value) +} + +func TestFixed8Flag_GetValue(t *testing.T) { + f := Fixed8Flag{Value: Fixed8{Value: fixedn.Fixed8(123)}} + require.Equal(t, "0.00000123", f.GetValue()) + require.True(t, f.TakesValue()) +} + +func TestFixed8Flag_RunAction(t *testing.T) { + called := false + action := func(ctx *cli.Context, s string) error { + called = true + require.Equal(t, "0.00000123", s) + return nil + } + app := cli.NewApp() + set := flag.NewFlagSet("test", flag.ContinueOnError) + ctx := cli.NewContext(app, set, nil) + f := Fixed8Flag{ + Action: action, + Value: Fixed8{Value: fixedn.Fixed8(123)}, + } + err := f.RunAction(ctx) + require.NoError(t, err) + require.True(t, called) +} + +func TestFixed8Flag_GetUsage(t *testing.T) { + f := Fixed8Flag{Usage: "Use this flag to specify gas amount"} + require.Equal(t, "Use this flag to specify gas amount", f.GetUsage()) +} + +func TestFixed8Flag_IsVisible(t *testing.T) { + f := Fixed8Flag{Hidden: false} + require.True(t, f.IsVisible()) + + f.Hidden = true + require.False(t, f.IsVisible()) +} + +func TestFixed8Flag_IsRequired(t *testing.T) { + f := Fixed8Flag{Required: false} + require.False(t, f.IsRequired()) + + f.Required = true + require.True(t, f.IsRequired()) +} diff --git a/cli/flags/util.go b/cli/flags/util.go deleted file mode 100644 index 6b215b5e2..000000000 --- a/cli/flags/util.go +++ /dev/null @@ -1,11 +0,0 @@ -package flags - -import "strings" - -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} diff --git a/cli/flags/util_test.go b/cli/flags/util_test.go deleted file mode 100644 index fb47b697f..000000000 --- a/cli/flags/util_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package flags - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestEachName(t *testing.T) { - expected := "*one*two*three" - actual := "" - - eachName(" one,two ,three", func(s string) { - actual += "*" + s - }) - require.Equal(t, expected, actual) -} diff --git a/cli/main.go b/cli/main.go index 7976815da..bc07e1477 100644 --- a/cli/main.go +++ b/cli/main.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os" "github.com/nspcc-dev/neo-go/cli/app" @@ -10,6 +11,7 @@ func main() { ctl := app.New() if err := ctl.Run(os.Args); err != nil { - panic(err) + fmt.Fprintln(ctl.ErrWriter, err) + os.Exit(1) } } 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 f934c5d8d..e7aa29393 100644 --- a/cli/nep_test/nep17_test.go +++ b/cli/nep_test/nep17_test.go @@ -21,14 +21,13 @@ func TestNEP17Balance(t *testing.T) { e := testcli.NewExecutor(t, true) args := []string{ - "neo-go", "wallet", "nep17", "multitransfer", + "neo-go", "wallet", "nep17", "multitransfer", "--force", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--wallet", testcli.ValidatorWallet, "--from", testcli.ValidatorAddr, "GAS:" + testcli.TestWalletMultiAccount1 + ":1", "NEO:" + testcli.TestWalletMultiAccount1 + ":10", "GAS:" + testcli.TestWalletMultiAccount3 + ":3", - "--force", } e.In.WriteString("one\r") e.Run(t, args...) @@ -114,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")...) }) } @@ -137,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() }) @@ -327,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/cli_options_test.go b/cli/options/cli_options_test.go index 5ba67a24d..4cf9977a5 100644 --- a/cli/options/cli_options_test.go +++ b/cli/options/cli_options_test.go @@ -8,7 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/internal/testcli" "github.com/stretchr/testify/require" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func TestGetRPCClient(t *testing.T) { diff --git a/cli/options/options.go b/cli/options/options.go index e9504569e..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" @@ -26,7 +27,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" "golang.org/x/term" @@ -47,69 +48,92 @@ const ( const RPCEndpointFlag = "rpc-endpoint" // Wallet is a set of flags used for wallet operations. -var Wallet = []cli.Flag{cli.StringFlag{ - Name: "wallet, w", - Usage: "wallet to use to get the key for transaction signing; conflicts with --wallet-config flag", -}, cli.StringFlag{ - Name: "wallet-config", - Usage: "path to wallet config to use to get the key for transaction signing; conflicts with --wallet flag"}, +var Wallet = []cli.Flag{ + &cli.StringFlag{ + Name: "wallet", + Aliases: []string{"w"}, + Usage: "Wallet to use to get the key for transaction signing; conflicts with --wallet-config flag", + }, + &cli.StringFlag{ + Name: "wallet-config", + Usage: "Path to wallet config to use to get the key for transaction signing; conflicts with --wallet flag", + }, } // Network is a set of flags for choosing the network to operate on // (privnet/mainnet/testnet). var Network = []cli.Flag{ - cli.BoolFlag{Name: "privnet, p", Usage: "use private network configuration (if --config-file option is not specified)"}, - cli.BoolFlag{Name: "mainnet, m", Usage: "use mainnet network configuration (if --config-file option is not specified)"}, - cli.BoolFlag{Name: "testnet, t", Usage: "use testnet network configuration (if --config-file option is not specified)"}, - cli.BoolFlag{Name: "unittest", Hidden: true}, + &cli.BoolFlag{ + Name: "privnet", + Aliases: []string{"p"}, + Usage: "Use private network configuration (if --config-file option is not specified)", + }, + &cli.BoolFlag{ + Name: "mainnet", + Aliases: []string{"m"}, + Usage: "Use mainnet network configuration (if --config-file option is not specified)", + }, + &cli.BoolFlag{ + Name: "testnet", + Aliases: []string{"t"}, + Usage: "Use testnet network configuration (if --config-file option is not specified)", + }, + &cli.BoolFlag{ + Name: "unittest", + Hidden: true, + }, } // RPC is a set of flags used for RPC connections (endpoint and timeout). var RPC = []cli.Flag{ - cli.StringFlag{ - Name: RPCEndpointFlag + ", r", - Usage: "RPC node address", + &cli.StringFlag{ + Name: RPCEndpointFlag, + Aliases: []string{"r"}, + Usage: "RPC node address", + Required: true, + Action: cmdargs.EnsureNotEmpty("rpc-endpoint"), }, - cli.DurationFlag{ - Name: "timeout, s", - Value: DefaultTimeout, - Usage: "Timeout for the operation", + &cli.DurationFlag{ + Name: "timeout", + Aliases: []string{"s"}, + Value: DefaultTimeout, + Usage: "Timeout for the operation", }, } // Historic is a flag for commands that can perform historic invocations. -var Historic = cli.StringFlag{ +var Historic = &cli.StringFlag{ Name: "historic", Usage: "Use historic state (height, block hash or state root hash)", } // Config is a flag for commands that use node configuration. -var Config = cli.StringFlag{ +var Config = &cli.StringFlag{ Name: "config-path", - Usage: "path to directory with per-network configuration files (may be overridden by --config-file option for the configuration file)", + Usage: "Path to directory with per-network configuration files (may be overridden by --config-file option for the configuration file)", } // ConfigFile is a flag for commands that use node configuration and provide // path to the specific config file instead of config path. -var ConfigFile = cli.StringFlag{ +var ConfigFile = &cli.StringFlag{ Name: "config-file", - Usage: "path to the node configuration file (overrides --config-path option)", + Usage: "Path to the node configuration file (overrides --config-path option)", } // RelativePath is a flag for commands that use node configuration and provide // a prefix to all relative paths in config files. -var RelativePath = cli.StringFlag{ +var RelativePath = &cli.StringFlag{ Name: "relative-path", - Usage: "a prefix to all relative paths in the node configuration file", + Usage: "Prefix to all relative paths in the node configuration file", } // Debug is a flag for commands that allow node in debug mode usage. -var Debug = cli.BoolFlag{ - Name: "debug, d", - Usage: "enable debug logging (LOTS of output, overrides configuration)", +var Debug = &cli.BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + 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") @@ -145,16 +169,13 @@ 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.NewExitError(errNoEndpoint, 1) - } c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{}) if err != nil { - return nil, cli.NewExitError(err, 1) + return nil, cli.Exit(err, 1) } err = c.Init() if err != nil { - return nil, cli.NewExitError(err, 1) + return nil, cli.Exit(err, 1) } return c, nil } @@ -173,7 +194,7 @@ func GetInvoker(c *rpcclient.Client, ctx *cli.Context, signers []transaction.Sig // 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) + return nil, cli.Exit(errInvalidHistoric, 1) } // GetRPCWithInvoker combines GetRPCClient with GetInvoker for cases where it's @@ -314,7 +335,7 @@ func GetRPCWithActor(gctx context.Context, ctx *cli.Context, signers []actor.Sig a, actorErr := actor.New(c, signers) if actorErr != nil { c.Close() - return nil, nil, cli.NewExitError(fmt.Errorf("failed to create Actor: %w", actorErr), 1) + return nil, nil, cli.Exit(fmt.Errorf("failed to create Actor: %w", actorErr), 1) } return c, a, nil } diff --git a/cli/options/options_test.go b/cli/options/options_test.go index 22ded55d6..649d9b26e 100644 --- a/cli/options/options_test.go +++ b/cli/options/options_test.go @@ -8,7 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/stretchr/testify/require" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func TestGetNetwork(t *testing.T) { diff --git a/cli/query/query.go b/cli/query/query.go index 5a63ebf32..f945a37fe 100644 --- a/cli/query/query.go +++ b/cli/query/query.go @@ -22,21 +22,22 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/vmstate" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // NewCommands returns 'query' command. -func NewCommands() []cli.Command { +func NewCommands() []*cli.Command { queryTxFlags := append([]cli.Flag{ - cli.BoolFlag{ - Name: "verbose, v", - Usage: "Output full tx info and execution logs", + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Output full tx info and execution logs", }, }, options.RPC...) - return []cli.Command{{ + return []*cli.Command{{ Name: "query", Usage: "Query data from RPC node", - Subcommands: []cli.Command{ + Subcommands: []*cli.Command{ { Name: "candidates", Usage: "Get candidates and votes", @@ -61,14 +62,14 @@ func NewCommands() []cli.Command { { Name: "tx", Usage: "Query transaction status", - UsageText: "neo-go query tx -r endpoint [-s timeout] [-v]", + UsageText: "neo-go query tx -r endpoint [-s timeout] [-v] ", Action: queryTx, Flags: queryTxFlags, }, { Name: "voter", Usage: "Print NEO holder account state", - UsageText: "neo-go query voter
-r endpoint [-s timeout]", + UsageText: "neo-go query voter -r endpoint [-s timeout]
", Action: queryVoter, Flags: options.RPC, }, @@ -77,16 +78,16 @@ func NewCommands() []cli.Command { } func queryTx(ctx *cli.Context) error { - args := ctx.Args() + args := ctx.Args().Slice() if len(args) == 0 { - return cli.NewExitError("Transaction hash is missing", 1) + return cli.Exit("transaction hash is missing", 1) } else if len(args) > 1 { - return cli.NewExitError("only one transaction hash is accepted", 1) + return cli.Exit("only one transaction hash is accepted", 1) } txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x")) if err != nil { - return cli.NewExitError(fmt.Sprintf("Invalid tx hash: %s", args[0]), 1) + return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -94,25 +95,25 @@ func queryTx(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } txOut, err := c.GetRawTransactionVerbose(txHash) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } var res *result.ApplicationLog if !txOut.Blockhash.Equals(util.Uint256{}) { res, err = c.GetApplicationLog(txHash, nil) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } } err = DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose")) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } return nil } @@ -179,16 +180,16 @@ func queryCandidates(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } vals, err := c.GetCandidates() if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } comm, err := c.GetCommittee() if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } sort.Slice(vals, func(i, j int) bool { @@ -225,12 +226,12 @@ func queryCommittee(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } comm, err := c.GetCommittee() if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } for _, k := range comm { @@ -251,12 +252,12 @@ func queryHeight(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } blockCount, err := c.GetBlockCount() if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } blockHeight := blockCount - 1 // GetBlockCount returns block count (including 0), not the highest block index. @@ -271,16 +272,16 @@ func queryHeight(ctx *cli.Context) error { } func queryVoter(ctx *cli.Context) error { - args := ctx.Args() + args := ctx.Args().Slice() if len(args) == 0 { - return cli.NewExitError("No address specified", 1) + return cli.Exit("no address specified", 1) } else if len(args) > 1 { - return cli.NewExitError("this command only accepts one address", 1) + return cli.Exit("this command only accepts one address", 1) } addr, err := flags.ParseAddress(args[0]) if err != nil { - return cli.NewExitError(fmt.Sprintf("wrong address: %s", args[0]), 1) + return cli.Exit(fmt.Sprintf("wrong address: %s", args[0]), 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -294,14 +295,14 @@ func queryVoter(ctx *cli.Context) error { st, err := neoToken.GetAccountState(addr) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if st == nil { st = new(state.NEOBalance) } dec, err := neoToken.Decimals() if err != nil { - return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to get decimals: %w", err), 1) } voted := "null" if st.VoteTo != nil { diff --git a/cli/server/server.go b/cli/server/server.go index d51b70159..882423405 100644 --- a/cli/server/server.go +++ b/cli/server/server.go @@ -27,13 +27,13 @@ import ( "github.com/nspcc-dev/neo-go/pkg/services/oracle" "github.com/nspcc-dev/neo-go/pkg/services/rpcsrv" "github.com/nspcc-dev/neo-go/pkg/services/stateroot" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) // NewCommands returns 'node' command. -func NewCommands() []cli.Command { +func NewCommands() []*cli.Command { cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath} cfgFlags = append(cfgFlags, options.Network...) var cfgWithCountFlags = make([]cli.Flag, len(cfgFlags)) @@ -41,75 +41,80 @@ func NewCommands() []cli.Command { cfgFlags = append(cfgFlags, options.Debug) cfgWithCountFlags = append(cfgWithCountFlags, - cli.UintFlag{ - Name: "count, c", - Usage: "number of blocks to be processed (default or 0: all chain)", + &cli.UintFlag{ + Name: "count", + Aliases: []string{"c"}, + Usage: "Number of blocks to be processed (default or 0: all chain)", }, ) var cfgCountOutFlags = make([]cli.Flag, len(cfgWithCountFlags)) copy(cfgCountOutFlags, cfgWithCountFlags) cfgCountOutFlags = append(cfgCountOutFlags, - cli.UintFlag{ - Name: "start, s", - Usage: "block number to start from (default: 0)", + &cli.UintFlag{ + Name: "start", + Aliases: []string{"s"}, + Usage: "Block number to start from", }, - cli.StringFlag{ - Name: "out, o", - Usage: "Output file (stdout if not given)", + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, + Usage: "Output file (stdout if not given)", }, ) var cfgCountInFlags = make([]cli.Flag, len(cfgWithCountFlags)) copy(cfgCountInFlags, cfgWithCountFlags) cfgCountInFlags = append(cfgCountInFlags, - cli.StringFlag{ - Name: "in, i", - Usage: "Input file (stdin if not given)", + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Usage: "Input file (stdin if not given)", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "dump", - Usage: "directory for storing JSON dumps", + Usage: "Directory for storing JSON dumps", }, - cli.BoolFlag{ - Name: "incremental, n", - Usage: "use if dump is incremental", + &cli.BoolFlag{ + Name: "incremental", + Aliases: []string{"n"}, + Usage: "Use if dump is incremental", }, ) var cfgHeightFlags = make([]cli.Flag, len(cfgFlags)+1) copy(cfgHeightFlags, cfgFlags) - cfgHeightFlags[len(cfgHeightFlags)-1] = cli.UintFlag{ + cfgHeightFlags[len(cfgHeightFlags)-1] = &cli.UintFlag{ Name: "height", Usage: "Height of the state to reset DB to", Required: true, } - return []cli.Command{ + return []*cli.Command{ { Name: "node", - Usage: "start a NeoGo node", + Usage: "Start a NeoGo node", UsageText: "neo-go node [--config-path path] [-d] [-p/-m/-t] [--config-file file]", Action: startServer, Flags: cfgFlags, }, { Name: "db", - Usage: "database manipulations", - Subcommands: []cli.Command{ + Usage: "Database manipulations", + Subcommands: []*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]", + 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]", 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]", + 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]", Action: restoreDB, Flags: cfgCountInFlags, }, { Name: "reset", - Usage: "reset database to the previous state", + Usage: "Reset database to the previous state", UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t] [--config-file file]", Action: resetDB, Flags: cfgHeightFlags, @@ -134,7 +139,7 @@ func newGraceContext() context.Context { func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *metrics.Service, *metrics.Service, error) { chain, _, err := initBlockChain(cfg, log) if err != nil { - return nil, nil, nil, cli.NewExitError(err, 1) + return nil, nil, nil, cli.Exit(err, 1) } prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log) pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log) @@ -142,11 +147,11 @@ func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *m go chain.Run() err = prometheus.Start() if err != nil { - return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Prometheus service: %w", err), 1) + return nil, nil, nil, cli.Exit(fmt.Errorf("failed to start Prometheus service: %w", err), 1) } err = pprof.Start() if err != nil { - return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Pprof service: %w", err), 1) + return nil, nil, nil, cli.Exit(fmt.Errorf("failed to start Pprof service: %w", err), 1) } return chain, prometheus, pprof, nil @@ -158,11 +163,11 @@ func dumpDB(ctx *cli.Context) error { } cfg, err := options.GetConfigFromContext(ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if logCloser != nil { defer func() { _ = logCloser() }() @@ -174,7 +179,7 @@ func dumpDB(ctx *cli.Context) error { if out := ctx.String("out"); out != "" { outStream, err = os.Create(out) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } } defer outStream.Close() @@ -192,7 +197,7 @@ func dumpDB(ctx *cli.Context) error { chainCount := chain.BlockHeight() + 1 if start+count > chainCount { - return cli.NewExitError(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainCount-1, count, start), 1) + return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainCount-1, count, start), 1) } if count == 0 { count = chainCount - start @@ -203,7 +208,7 @@ func dumpDB(ctx *cli.Context) error { writer.WriteU32LE(count) err = chaindump.Dump(chain, writer, start, count) if err != nil { - return cli.NewExitError(err.Error(), 1) + return cli.Exit(err.Error(), 1) } return nil } @@ -218,7 +223,7 @@ func restoreDB(ctx *cli.Context) error { } log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if logCloser != nil { defer func() { _ = logCloser() }() @@ -229,7 +234,7 @@ func restoreDB(ctx *cli.Context) error { if in := ctx.String("in"); in != "" { inStream, err = os.Open(in) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } } defer inStream.Close() @@ -254,7 +259,7 @@ func restoreDB(ctx *cli.Context) error { if ctx.Bool("incremental") { start = reader.ReadU32LE() if chain.BlockHeight()+1 < start { - return cli.NewExitError(fmt.Errorf("expected height: %d, dump starts at %d", + return cli.Exit(fmt.Errorf("expected height: %d, dump starts at %d", chain.BlockHeight()+1, start), 1) } } @@ -266,10 +271,10 @@ func restoreDB(ctx *cli.Context) error { var allBlocks = reader.ReadU32LE() if reader.Err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if skip+count > allBlocks { - return cli.NewExitError(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1) + return cli.Exit(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1) } if count == 0 { count = allBlocks - skip @@ -320,7 +325,7 @@ func restoreDB(ctx *cli.Context) error { err = chaindump.Restore(chain, reader, skip, count, f) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } return nil } @@ -331,29 +336,29 @@ func resetDB(ctx *cli.Context) error { } cfg, err := options.GetConfigFromContext(ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } h := uint32(ctx.Uint("height")) log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if logCloser != nil { defer func() { _ = logCloser() }() } chain, store, err := initBlockChain(cfg, log) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create Blockchain instance: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to create Blockchain instance: %w", err), 1) } err = chain.Reset(h) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1) + return cli.Exit(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1) } err = store.Close() if err != nil { - return cli.NewExitError(fmt.Errorf("failed to close the DB: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to close the DB: %w", err), 1) } return nil } @@ -442,12 +447,12 @@ func startServer(ctx *cli.Context) error { cfg, err := options.GetConfigFromContext(ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } var logDebug = ctx.Bool("debug") log, logLevel, logCloser, err := options.HandleLoggingParams(logDebug, cfg.ApplicationConfiguration) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if logCloser != nil { defer func() { _ = logCloser() }() @@ -458,12 +463,12 @@ func startServer(ctx *cli.Context) error { serverConfig, err := network.NewServerConfig(cfg) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } chain, prometheus, pprof, err := initBCWithMetrics(cfg, log) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } defer func() { pprof.ShutDown() @@ -473,26 +478,26 @@ func startServer(ctx *cli.Context) error { serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create network server: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to create network server: %w", err), 1) } srMod := chain.GetStateModule().(*corestate.Module) // Take full responsibility here. sr, err := stateroot.New(serverConfig.StateRootCfg, srMod, log, chain, serv.BroadcastExtensible) if err != nil { - return cli.NewExitError(fmt.Errorf("can't initialize StateRoot service: %w", err), 1) + return cli.Exit(fmt.Errorf("can't initialize StateRoot service: %w", err), 1) } serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload) oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } errChan := make(chan error) rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan) @@ -640,7 +645,7 @@ Main: } if shutdownErr != nil { - return cli.NewExitError(shutdownErr, 1) + return cli.Exit(shutdownErr, 1) } return nil @@ -650,7 +655,7 @@ Main: func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, error) { store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration) if err != nil { - return nil, nil, cli.NewExitError(fmt.Errorf("could not initialize storage: %w", err), 1) + return nil, nil, cli.Exit(fmt.Errorf("could not initialize storage: %w", err), 1) } chain, err := core.NewBlockchain(store, cfg.Blockchain(), log) @@ -663,7 +668,7 @@ func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, stora errArgs = append(errArgs, closeErr) } - return nil, nil, cli.NewExitError(fmt.Errorf(errText, errArgs...), 1) + return nil, nil, cli.Exit(fmt.Errorf(errText, errArgs...), 1) } return chain, store, nil } diff --git a/cli/server/server_test.go b/cli/server/server_test.go index f9084345d..8eb1de819 100644 --- a/cli/server/server_test.go +++ b/cli/server/server_test.go @@ -15,7 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" "github.com/stretchr/testify/require" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "gopkg.in/yaml.v3" ) diff --git a/cli/smartcontract/contract_test.go b/cli/smartcontract/contract_test.go index 481c1a21b..ac4648eb7 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", + e.RunWithErrorCheck(t, `invalid value "not-a-sender" for flag -sender: invalid base58 digit ('-')`, "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 28f9f18ce..03a2803ca 100644 --- a/cli/smartcontract/generate.go +++ b/cli/smartcontract/generate.go @@ -9,34 +9,39 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" "github.com/nspcc-dev/neo-go/pkg/smartcontract/rpcbinding" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "gopkg.in/yaml.v3" ) var generatorFlags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Configuration file to use", + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Configuration file to use", }, - cli.StringFlag{ - Name: "manifest, m", + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, Required: true, Usage: "Read contract manifest (*.manifest.json) file", + Action: cmdargs.EnsureNotEmpty("manifest"), }, - cli.StringFlag{ - Name: "out, o", + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, Required: true, Usage: "Output of the compiled wrapper", + Action: cmdargs.EnsureNotEmpty("out"), }, - cli.StringFlag{ + &cli.StringFlag{ Name: "hash", Usage: "Smart-contract hash. If not passed, the wrapper will be designed for dynamic hash usage", }, } -var generateWrapperCmd = cli.Command{ +var generateWrapperCmd = &cli.Command{ Name: "generate-wrapper", - Usage: "generate wrapper to use in other contracts", + Usage: "Generate wrapper to use in other contracts", UsageText: "neo-go contract generate-wrapper --manifest --out [--hash ] [--config ]", Description: `Generates a Go wrapper to use it in other smart contracts. If the --hash flag is provided, CALLT instruction is used for the target contract @@ -48,9 +53,9 @@ var generateWrapperCmd = cli.Command{ Flags: generatorFlags, } -var generateRPCWrapperCmd = cli.Command{ +var generateRPCWrapperCmd = &cli.Command{ Name: "generate-rpcwrapper", - Usage: "generate RPC wrapper to use for data reads", + Usage: "Generate RPC wrapper to use for data reads", UsageText: "neo-go contract generate-rpcwrapper --manifest --out [--hash ] [--config ]", Action: contractGenerateRPCWrapper, Flags: generatorFlags, @@ -76,23 +81,23 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error) if hStr := ctx.String("hash"); len(hStr) != 0 { h, err = util.Uint160DecodeStringLE(strings.TrimPrefix(hStr, "0x")) if err != nil { - return cli.NewExitError(fmt.Errorf("invalid contract hash: %w", err), 1) + return cli.Exit(fmt.Errorf("invalid contract hash: %w", err), 1) } } m, _, err := readManifest(ctx.String("manifest"), h) if err != nil { - return cli.NewExitError(fmt.Errorf("can't read contract manifest: %w", err), 1) + return cli.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1) } cfg := binding.NewConfig() if cfgPath := ctx.String("config"); cfgPath != "" { bs, err := os.ReadFile(cfgPath) if err != nil { - return cli.NewExitError(fmt.Errorf("can't read config file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't read config file: %w", err), 1) } err = yaml.Unmarshal(bs, &cfg) if err != nil { - return cli.NewExitError(fmt.Errorf("can't parse config file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't parse config file: %w", err), 1) } } cfg.Manifest = m @@ -100,7 +105,7 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error) f, err := os.Create(ctx.String("out")) if err != nil { - return cli.NewExitError(fmt.Errorf("can't create output file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't create output file: %w", err), 1) } defer f.Close() @@ -108,7 +113,7 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error) err = cb(cfg) if err != nil { - return cli.NewExitError(fmt.Errorf("error during generation: %w", err), 1) + return cli.Exit(fmt.Errorf("error during generation: %w", err), 1) } return nil } diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index 195be3d08..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" ) 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 e940b333b..8d65fcc05 100644 --- a/cli/smartcontract/manifest.go +++ b/cli/smartcontract/manifest.go @@ -2,7 +2,6 @@ package smartcontract import ( "encoding/json" - "errors" "fmt" "os" @@ -13,34 +12,30 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func manifestAddGroup(ctx *cli.Context) error { if err := cmdargs.EnsureNone(ctx); err != nil { return err } - sender, err := flags.ParseAddress(ctx.String("sender")) - if err != nil { - return cli.NewExitError(fmt.Errorf("invalid sender: %w", err), 1) - } - + sender := ctx.Generic("sender").(*flags.Address) nf, _, err := readNEFFile(ctx.String("nef")) if err != nil { - return cli.NewExitError(fmt.Errorf("can't read NEF file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't read NEF file: %w", err), 1) } mPath := ctx.String("manifest") m, _, err := readManifest(mPath, util.Uint160{}) if err != nil { - return cli.NewExitError(fmt.Errorf("can't read contract manifest: %w", err), 1) + return cli.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1) } - h := state.CreateContractHash(sender, nf.Checksum, m.Name) + h := state.CreateContractHash(sender.Uint160(), nf.Checksum, m.Name) gAcc, w, err := options.GetAccFromContext(ctx) if err != nil { - return cli.NewExitError(fmt.Errorf("can't get account to sign group with: %w", err), 1) + return cli.Exit(fmt.Errorf("can't get account to sign group with: %w", err), 1) } defer w.Close() @@ -64,21 +59,17 @@ func manifestAddGroup(ctx *cli.Context) error { rawM, err := json.Marshal(m) if err != nil { - return cli.NewExitError(fmt.Errorf("can't marshal manifest: %w", err), 1) + return cli.Exit(fmt.Errorf("can't marshal manifest: %w", err), 1) } err = os.WriteFile(mPath, rawM, os.ModePerm) if err != nil { - return cli.NewExitError(fmt.Errorf("can't write manifest file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't write manifest file: %w", err), 1) } return nil } 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 +87,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 87b04ed95..c3c8d4032 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -27,25 +27,27 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "gopkg.in/yaml.v3" ) -// addressFlagName is a flag name used for address-related operations. It should be -// the same within the smartcontract package, thus, use this constant. -const addressFlagName = "address, a" +// addressFlagName and addressFlagAlias are a flag name and its alias +// used for address-related operations. It should be the same within +// the smartcontract package, thus, use this constant. +const ( + addressFlagName = "address" + addressFlagAlias = "a" +) 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{ - Name: addressFlagName, - Usage: "address to use as transaction signee (and gas source)", + 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)", } ) @@ -74,11 +76,14 @@ func RuntimeNotify(args []any) { ) // NewCommands returns 'contract' command. -func NewCommands() []cli.Command { +func NewCommands() []*cli.Command { testInvokeScriptFlags := []cli.Flag{ - cli.StringFlag{ - Name: "in, i", - Usage: "Input location of the .nef file that needs to be invoked", + &cli.StringFlag{ + 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, } @@ -96,40 +101,56 @@ func NewCommands() []cli.Command { invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...) invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) deployFlags := append(invokeFunctionFlags, []cli.Flag{ - cli.StringFlag{ - Name: "in, i", - Usage: "Input file for the smart contract (*.nef)", + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file for the smart contract (*.nef)", + Action: cmdargs.EnsureNotEmpty("in"), }, - cli.StringFlag{ - Name: "manifest, m", - Usage: "Manifest input file (*.manifest.json)", + &cli.StringFlag{ + 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, s", - Usage: "deploy transaction sender", + &flags.AddressFlag{ + Name: "sender", + Aliases: []string{"s"}, + Required: true, + Usage: "Deploy transaction sender", }, - flags.AddressFlag{ - Name: addressFlagName, // use the same name for handler code unification. - Usage: "account to sign group with", + &flags.AddressFlag{ + 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, n", - Usage: "path to the NEF file", + &cli.StringFlag{ + Name: "nef", + Aliases: []string{"n"}, + Required: true, + Usage: "Path to the NEF file", + Action: cmdargs.EnsureNotEmpty("nef"), }, - cli.StringFlag{ - Name: "manifest, m", - Usage: "path to the manifest", + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Path to the manifest", + Action: cmdargs.EnsureNotEmpty("manifest"), }, }, options.Wallet...) - return []cli.Command{{ + return []*cli.Command{{ Name: "contract", - Usage: "compile - debug - deploy smart contracts", - Subcommands: []cli.Command{ + Usage: "Compile - debug - deploy smart contracts", + Subcommands: []*cli.Command{ { Name: "compile", - Usage: "compile a smart contract to a .nef file", + Usage: "Compile a smart contract to a .nef file", UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions] [--guess-eventtypes]", Description: `Compiles given smart contract to a .nef file and emits other associated information (manifest, bindings configuration, debug information files) if @@ -141,55 +162,63 @@ func NewCommands() []cli.Command { `, Action: contractCompile, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "in, i", - Usage: "Input file for the smart contract to be compiled (*.go file or directory)", + &cli.StringFlag{ + 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, o", - Usage: "Output of the compiled contract", + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, + Usage: "Output of the compiled contract", }, - cli.BoolFlag{ - Name: "verbose, v", - Usage: "Print out additional information after a compiling", + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "Print out additional information after a compiling", }, - cli.StringFlag{ - Name: "debug, d", - Usage: "Emit debug info in a separate file", + &cli.StringFlag{ + Name: "debug", + Aliases: []string{"d"}, + Usage: "Emit debug info in a separate file", }, - cli.StringFlag{ - Name: "manifest, m", - Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)", + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)", }, - cli.StringFlag{ - Name: "config, c", - Usage: "Configuration input file (*.yml)", + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Configuration input file (*.yml)", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "no-standards", - Usage: "do not check compliance with supported standards", + Usage: "Do not check compliance with supported standards", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "no-events", - Usage: "do not check emitted events with the manifest", + Usage: "Do not check emitted events with the manifest", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "no-permissions", - Usage: "do not check if invoked contracts are allowed in manifest", + Usage: "Do not check if invoked contracts are allowed in manifest", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "guess-eventtypes", - Usage: "guess event types for smart-contract bindings configuration from the code usages", + Usage: "Guess event types for smart-contract bindings configuration from the code usages", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "bindings", - Usage: "output file for smart-contract bindings configuration", + Usage: "Output file for smart-contract bindings configuration", }, }, }, { Name: "deploy", - Usage: "deploy a smart contract (.nef with description)", + Usage: "Deploy a smart contract (.nef with description)", UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [data]", Description: `Deploys given contract into the chain. The gas parameter is for additional gas to be added as a network fee to prioritize the transaction. The data @@ -204,7 +233,7 @@ func NewCommands() []cli.Command { generateRPCWrapperCmd, { Name: "invokefunction", - Usage: "invoke deployed contract on the blockchain", + Usage: "Invoke deployed contract on the blockchain", UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [method] [arguments...] [--] [signers...]", Description: `Executes given (as a script hash) deployed script with the given method, arguments and signers. Sender is included in the list of signers by default @@ -219,7 +248,7 @@ func NewCommands() []cli.Command { }, { Name: "testinvokefunction", - Usage: "invoke deployed contract on the blockchain (test mode)", + Usage: "Invoke deployed contract on the blockchain (test mode)", 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 @@ -250,63 +279,78 @@ func NewCommands() []cli.Command { }, { Name: "init", - Usage: "initialize a new smart-contract in a directory with boiler plate code", + Usage: "Initialize a new smart-contract in a directory with boiler plate code", UsageText: "neo-go contract init -n name [--skip-details]", Action: initSmartContract, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "name, n", - Usage: "name of the smart-contract to be initialized", + &cli.StringFlag{ + 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, skip", - Usage: "skip filling in the projects and contract details", + &cli.BoolFlag{ + Name: "skip-details", + Aliases: []string{"skip"}, + Usage: "Skip filling in the projects and contract details", }, }, }, { Name: "inspect", - Usage: "creates a user readable dump of the program instructions", + Usage: "Creates a user readable dump of the program instructions", UsageText: "neo-go contract inspect -i file [-c]", Action: inspect, Flags: []cli.Flag{ - cli.BoolFlag{ - Name: "compile, c", - Usage: "compile input file (it should be go code then)", + &cli.BoolFlag{ + Name: "compile", + Aliases: []string{"c"}, + Usage: "Compile input file (it should be go code then)", }, - cli.StringFlag{ - Name: "in, i", - Usage: "input file of the program (either .go or .nef)", + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Required: true, + Usage: "Input file of the program (either .go or .nef)", + Action: cmdargs.EnsureNotEmpty("in"), }, }, }, { Name: "calc-hash", - Usage: "calculates hash of a contract after deployment", + Usage: "Calculates hash of a contract after deployment", UsageText: "neo-go contract calc-hash -i nef -m manifest -s address", Action: calcHash, Flags: []cli.Flag{ - flags.AddressFlag{ - Name: "sender, s", - Usage: "sender script hash or address", + &flags.AddressFlag{ + Name: "sender", + Aliases: []string{"s"}, + Required: true, + Usage: "Sender script hash or address", }, - cli.StringFlag{ - Name: "in", - Usage: "path to NEF file", + &cli.StringFlag{ + Name: "in", + Required: true, + Usage: "Path to NEF file", + Action: cmdargs.EnsureNotEmpty("in"), }, - cli.StringFlag{ - Name: "manifest, m", - Usage: "path to manifest file", + &cli.StringFlag{ + Name: "manifest", + Aliases: []string{"m"}, + Required: true, + Usage: "Path to manifest file", + Action: cmdargs.EnsureNotEmpty("manifest"), }, }, }, { Name: "manifest", - Usage: "manifest-related commands", - Subcommands: []cli.Command{ + Usage: "Manifest-related commands", + Subcommands: []*cli.Command{ { Name: "add-group", - Usage: "adds group to the manifest", + Usage: "Adds group to the manifest", UsageText: "neo-go contract manifest add-group -w wallet [--wallet-config path] -n nef -m manifest -a address -s address", Action: manifestAddGroup, Flags: manifestAddGroupFlags, @@ -323,13 +367,10 @@ func initSmartContract(ctx *cli.Context) error { return err } contractName := ctx.String("name") - if contractName == "" { - return cli.NewExitError(errNoSmartContractName, 1) - } // Check if the file already exists, if yes, exit if _, err := os.Stat(contractName); err == nil { - return cli.NewExitError(errFileExist, 1) + return cli.Exit(errFileExist, 1) } basePath := contractName @@ -338,7 +379,7 @@ func initSmartContract(ctx *cli.Context) error { // create base directory if err := os.Mkdir(basePath, os.ModePerm); err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } m := ProjectConfig{ @@ -363,10 +404,10 @@ func initSmartContract(ctx *cli.Context) error { } b, err := yaml.Marshal(m) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if err := os.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } ver := ModVersion @@ -382,12 +423,12 @@ require ( github.com/nspcc-dev/neo-go/pkg/interop ` + ver + ` )`) if err := os.WriteFile(filepath.Join(basePath, "go.mod"), gm, 0644); err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) if err := os.WriteFile(filepath.Join(basePath, fileName), data, 0644); err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } fmt.Fprintf(ctx.App.Writer, "Successfully initialized smart contract [%s]\n", contractName) @@ -400,16 +441,13 @@ func contractCompile(ctx *cli.Context) error { return err } src := ctx.String("in") - if len(src) == 0 { - return cli.NewExitError(errNoInput, 1) - } manifestFile := ctx.String("manifest") confFile := ctx.String("config") debugFile := ctx.String("debug") out := ctx.String("out") bindings := ctx.String("bindings") if len(confFile) == 0 && (len(manifestFile) != 0 || len(debugFile) != 0 || len(bindings) != 0) { - return cli.NewExitError(errNoConfFile, 1) + return cli.Exit(errNoConfFile, 1) } autocomplete := len(manifestFile) == 0 && len(confFile) == 0 && @@ -419,7 +457,7 @@ func contractCompile(ctx *cli.Context) error { var root string fileInfo, err := os.Stat(src) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to stat source file or directory: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to stat source file or directory: %w", err), 1) } if fileInfo.IsDir() { base := filepath.Base(fileInfo.Name()) @@ -470,7 +508,7 @@ func contractCompile(ctx *cli.Context) error { result, err := compiler.CompileAndSave(src, o) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if ctx.Bool("verbose") { fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result)) @@ -484,34 +522,25 @@ func calcHash(ctx *cli.Context) error { return err } sender := ctx.Generic("sender").(*flags.Address) - if !sender.IsSet { - return cli.NewExitError("sender is not set", 1) - } - p := ctx.String("in") - if p == "" { - return cli.NewExitError(errors.New("no .nef file was provided"), 1) - } mpath := ctx.String("manifest") - if mpath == "" { - return cli.NewExitError(errors.New("no manifest file provided"), 1) - } + f, err := os.ReadFile(p) if err != nil { - return cli.NewExitError(fmt.Errorf("can't read .nef file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't read .nef file: %w", err), 1) } nefFile, err := nef.FileFromBytes(f) if err != nil { - return cli.NewExitError(fmt.Errorf("can't unmarshal .nef file: %w", err), 1) + return cli.Exit(fmt.Errorf("can't unmarshal .nef file: %w", err), 1) } manifestBytes, err := os.ReadFile(mpath) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to read manifest file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1) } m := &manifest.Manifest{} err = json.Unmarshal(manifestBytes, m) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to restore manifest file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to restore manifest file: %w", err), 1) } fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(sender.Uint160(), nefFile.Checksum, m.Name).StringLE()) return nil @@ -528,7 +557,7 @@ func invokeFunction(ctx *cli.Context) error { func invokeInternal(ctx *cli.Context, signAndPush bool) error { var ( err error - exitErr *cli.ExitError + exitErr cli.ExitCoder operation string params []any paramsStart = 1 @@ -539,22 +568,23 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { args := ctx.Args() if !args.Present() { - return cli.NewExitError(errNoScriptHash, 1) + return cli.Exit(errNoScriptHash, 1) } - script, err := flags.ParseAddress(args[0]) + argsSlice := args.Slice() + script, err := flags.ParseAddress(argsSlice[0]) if err != nil { - return cli.NewExitError(fmt.Errorf("incorrect script hash: %w", err), 1) + return cli.Exit(fmt.Errorf("incorrect script hash: %w", err), 1) } - if len(args) <= 1 { - return cli.NewExitError(errNoMethod, 1) + if len(argsSlice) <= 1 { + return cli.Exit(errNoMethod, 1) } - operation = args[1] + operation = argsSlice[1] paramsStart++ - if len(args) > paramsStart { - cosignersOffset, scParams, err = cmdargs.ParseParams(args[paramsStart:], true) + if len(argsSlice) > paramsStart { + cosignersOffset, scParams, err = cmdargs.ParseParams(argsSlice[paramsStart:], true) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } params = make([]any, len(scParams)) for i := range scParams { @@ -575,7 +605,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { if signAndPush { acc, w, err = options.GetAccFromContext(ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } defer w.Close() } @@ -595,7 +625,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, if signAndPush { signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None) if err != nil { - return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) + return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1) } } gctx, cancel := options.GetTimeoutContext(ctx) @@ -615,12 +645,12 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, out := ctx.String("out") resp, err = inv.Call(script, operation, params...) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(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 cli.NewExitError(errText, 1) + return cli.Exit(errText, 1) } action := "send" @@ -630,42 +660,38 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, process = "Saving" } if !ctx.Bool("force") { - return cli.NewExitError(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1) + return cli.Exit(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 cli.NewExitError(err, 1) + return cli.Exit(err, 1) } fmt.Fprintln(ctx.App.Writer, string(b)) return nil } if len(resp.Script) == 0 { - return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) + return cli.Exit(errors.New("no script returned from the RPC node"), 1) } tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed, nil) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to create tx: %w", err), 1) } return txctx.SignAndSend(ctx, act, acc, tx) } func testInvokeScript(ctx *cli.Context) error { src := ctx.String("in") - if len(src) == 0 { - return cli.NewExitError(errNoInput, 1) - } - b, err := os.ReadFile(src) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } nefFile, err := nef.FileFromBytes(b) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to restore .nef file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1) } signers, exitErr := cmdargs.GetSignersFromContext(ctx, 0) @@ -683,12 +709,12 @@ func testInvokeScript(ctx *cli.Context) error { resp, err := inv.Run(nefFile.Script) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } b, err = json.MarshalIndent(resp, "", " ") if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } fmt.Fprintln(ctx.App.Writer, string(b)) @@ -714,9 +740,6 @@ func inspect(ctx *cli.Context) error { } in := ctx.String("in") compile := ctx.Bool("compile") - if len(in) == 0 { - return cli.NewExitError(errNoInput, 1) - } var ( b []byte err error @@ -724,16 +747,16 @@ func inspect(ctx *cli.Context) error { if compile { b, err = compiler.Compile(in, nil) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to compile: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to compile: %w", err), 1) } } else { f, err := os.ReadFile(in) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to read .nef file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to read .nef file: %w", err), 1) } nefFile, err := nef.FileFromBytes(f) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to restore .nef file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1) } b = nefFile.Script } @@ -748,22 +771,22 @@ func inspect(ctx *cli.Context) error { func contractDeploy(ctx *cli.Context) error { nefFile, f, err := readNEFFile(ctx.String("in")) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } m, manifestBytes, err := readManifest(ctx.String("manifest"), util.Uint160{}) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to read manifest file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1) } var appCallParams = []any{f, manifestBytes} - signOffset, data, err := cmdargs.ParseParams(ctx.Args(), true) + signOffset, data, err := cmdargs.ParseParams(ctx.Args().Slice(), true) if err != nil { - return cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) + return cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1) } if len(data) > 1 { - return cli.NewExitError("'data' should be represented as a single parameter", 1) + return cli.Exit("'data' should be represented as a single parameter", 1) } if len(data) != 0 { appCallParams = append(appCallParams, data[0]) @@ -771,7 +794,7 @@ func contractDeploy(ctx *cli.Context) error { acc, w, err := options.GetAccFromContext(ctx) if err != nil { - return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1) + return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1) } defer w.Close() sender := acc.ScriptHash() @@ -801,12 +824,12 @@ func ParseContractConfig(confFile string) (ProjectConfig, error) { conf := ProjectConfig{} confBytes, err := os.ReadFile(confFile) if err != nil { - return conf, cli.NewExitError(err, 1) + return conf, cli.Exit(err, 1) } err = yaml.Unmarshal(confBytes, &conf) if err != nil { - return conf, cli.NewExitError(fmt.Errorf("bad config: %w", err), 1) + return conf, cli.Exit(fmt.Errorf("bad config: %w", err), 1) } return conf, nil } diff --git a/cli/smartcontract/smart_contract_test.go b/cli/smartcontract/smart_contract_test.go index 69717b37c..7bab33645 100644 --- a/cli/smartcontract/smart_contract_test.go +++ b/cli/smartcontract/smart_contract_test.go @@ -9,7 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/stretchr/testify/require" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "gopkg.in/yaml.v3" ) diff --git a/cli/txctx/tx.go b/cli/txctx/tx.go index a012d10f8..805de75ca 100644 --- a/cli/txctx/tx.go +++ b/cli/txctx/tx.go @@ -16,34 +16,36 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) var ( // GasFlag is a flag used for the additional network fee. - GasFlag = flags.Fixed8Flag{ - Name: "gas, g", - Usage: "network fee to add to the transaction (prioritizing it)", + GasFlag = &flags.Fixed8Flag{ + Name: "gas", + Aliases: []string{"g"}, + Usage: "Network fee to add to the transaction (prioritizing it)", } // SysGasFlag is a flag used for the additional system fee. - SysGasFlag = flags.Fixed8Flag{ - Name: "sysgas, e", - Usage: "system fee to add to the transaction (compensating for execution)", + SysGasFlag = &flags.Fixed8Flag{ + Name: "sysgas", + Aliases: []string{"e"}, + Usage: "System fee to add to the transaction (compensating for execution)", } // OutFlag is a flag used for file output. - OutFlag = cli.StringFlag{ + OutFlag = &cli.StringFlag{ Name: "out", - Usage: "file (JSON) to put signature context with a transaction to", + Usage: "File (JSON) to put signature context with a transaction to", } // ForceFlag is a flag used to force transaction send. - ForceFlag = cli.BoolFlag{ + ForceFlag = &cli.BoolFlag{ Name: "force", Usage: "Do not ask for a confirmation (and ignore errors)", } // AwaitFlag is a flag used to wait for the transaction to be included in a block. - AwaitFlag = cli.BoolFlag{ + AwaitFlag = &cli.BoolFlag{ Name: "await", - Usage: "wait for the transaction to be included in a block", + Usage: "Wait for the transaction to be included in a block", } ) @@ -71,7 +73,7 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr promptTime := time.Now() err := input.ConfirmTx(ctx.App.Writer, tx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } waitTime := time.Since(promptTime) // Compensate for confirmation waiting. @@ -83,17 +85,17 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr ) resTx, vub, err = act.SignAndSend(tx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if ctx.Bool("await") { aer, err = act.Wait(resTx, vub, err) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1) + return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1) } } } if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer) diff --git a/cli/util/cancel.go b/cli/util/cancel.go index fbb112ad8..fcb56a059 100644 --- a/cli/util/cancel.go +++ b/cli/util/cancel.go @@ -16,20 +16,20 @@ import ( "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func cancelTx(ctx *cli.Context) error { - args := ctx.Args() + args := ctx.Args().Slice() if len(args) == 0 { - return cli.NewExitError("transaction hash is missing", 1) + return cli.Exit("transaction hash is missing", 1) } else if len(args) > 1 { - return cli.NewExitError("only one transaction hash is accepted", 1) + return cli.Exit("only one transaction hash is accepted", 1) } txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x")) if err != nil { - return cli.NewExitError(fmt.Sprintf("invalid tx hash: %s", args[0]), 1) + return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -37,13 +37,13 @@ func cancelTx(ctx *cli.Context) error { acc, w, err := options.GetAccFromContext(ctx) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1) } defer w.Close() signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry) if err != nil { - return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) + return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1) } c, a, exitErr := options.GetRPCWithActor(gctx, ctx, signers) if exitErr != nil { @@ -52,11 +52,11 @@ func cancelTx(ctx *cli.Context) error { mainTx, _ := c.GetRawTransactionVerbose(txHash) if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) { - return cli.NewExitError(fmt.Errorf("target transaction %s is accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1) + return cli.Exit(fmt.Errorf("target transaction %s is accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1) } if mainTx != nil && !mainTx.HasSigner(acc.ScriptHash()) { - return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1) + return cli.Exit(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1) } resHash, resVub, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error { @@ -74,7 +74,7 @@ func cancelTx(ctx *cli.Context) error { return nil }) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to send conflicting transaction: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to send conflicting transaction: %w", err), 1) } var res *state.AppExecResult if ctx.Bool("await") { @@ -82,19 +82,19 @@ func cancelTx(ctx *cli.Context) error { if err != nil { if errors.Is(err, waiter.ErrTxNotAccepted) { if mainTx == nil { - return cli.NewExitError(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1) + return cli.Exit(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1) } fmt.Fprintf(ctx.App.Writer, "Neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of both target and conflicting transactions). Main transaction is not valid anymore, cancellation is successful\n", resVub) return nil } - return cli.NewExitError(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1) + return cli.Exit(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1) } if txHash.Equals(res.Container) { tx, err := c.GetRawTransactionVerbose(txHash) if err != nil { - return cli.NewExitError(fmt.Errorf("target transaction %s is accepted", txHash), 1) + return cli.Exit(fmt.Errorf("target transaction %s is accepted", txHash), 1) } - return cli.NewExitError(fmt.Errorf("target transaction %s is accepted at block %s", txHash, tx.Blockhash.StringLE()), 1) + return cli.Exit(fmt.Errorf("target transaction %s is accepted at block %s", txHash, tx.Blockhash.StringLE()), 1) } fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted") } diff --git a/cli/util/convert.go b/cli/util/convert.go index 4205edd9a..26c520877 100644 --- a/cli/util/convert.go +++ b/cli/util/convert.go @@ -11,27 +11,32 @@ import ( "github.com/nspcc-dev/neo-go/cli/txctx" vmcli "github.com/nspcc-dev/neo-go/cli/vm" "github.com/nspcc-dev/neo-go/pkg/vm" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // NewCommands returns util commands for neo-go CLI. -func NewCommands() []cli.Command { - txDumpFlags := append([]cli.Flag{}, options.RPC...) +func NewCommands() []*cli.Command { + // 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{ - Name: "address, a", - Usage: "address to use as conflicting transaction signee (and gas source)", + &flags.AddressFlag{ + Name: "address", + Aliases: []string{"a"}, + Usage: "Address to use as conflicting transaction signee (and gas source)", }, txctx.GasFlag, txctx.AwaitFlag, }, options.RPC...) txCancelFlags = append(txCancelFlags, options.Wallet...) - return []cli.Command{ + return []*cli.Command{ { Name: "util", Usage: "Various helper commands", - Subcommands: []cli.Command{ + Subcommands: []*cli.Command{ { Name: "convert", Usage: "Convert provided argument into other possible formats", @@ -44,7 +49,7 @@ func NewCommands() []cli.Command { { Name: "sendtx", Usage: "Send complete transaction stored in a context file", - UsageText: "sendtx [-r ] [--await]", + UsageText: "sendtx [-r ] [--await] ", Description: `Sends the transaction from the given context file to the given RPC node if it's completely signed and ready. This command expects a ContractParametersContext JSON file for input, it can't handle binary (or hex- or base64-encoded) @@ -57,7 +62,7 @@ func NewCommands() []cli.Command { { Name: "canceltx", Usage: "Cancel transaction by sending conflicting transaction", - UsageText: "canceltx -r --wallet [--account ] [--wallet-config ] [--gas ] [--await]", + UsageText: "canceltx -r --wallet [--address ] [--wallet-config ] [--gas ] [--await] ", Description: `Aims to prevent a transaction from being added to the blockchain by dispatching a more prioritized conflicting transaction to the specified RPC node. The input for this command should be the transaction hash. If another account is not specified, the conflicting transaction is @@ -90,16 +95,17 @@ func NewCommands() []cli.Command { { Name: "ops", Usage: "Pretty-print VM opcodes of the given base64- or hex- encoded script (base64 is checked first). If the input file is specified, then the script is taken from the file.", - UsageText: "ops [-i path-to-file] [--hex]", + UsageText: "ops [-i path-to-file] [--hex] ", Action: handleOps, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "in, i", - Usage: "input file containing base64- or hex- encoded script representation", + &cli.StringFlag{ + Name: "in", + Aliases: []string{"i"}, + Usage: "Input file containing base64- or hex- encoded script representation", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "hex", - Usage: "use hex encoding and do not check base64", + Usage: "Use hex encoding and do not check base64", }, }, }, @@ -109,9 +115,9 @@ func NewCommands() []cli.Command { } func handleParse(ctx *cli.Context) error { - res, err := vmcli.Parse(ctx.Args()) + res, err := vmcli.Parse(ctx.Args().Slice()) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } fmt.Fprint(ctx.App.Writer, res) return nil @@ -127,21 +133,21 @@ func handleOps(ctx *cli.Context) error { if len(in) != 0 { b, err := os.ReadFile(in) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to read file: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to read file: %w", err), 1) } s = string(b) } else { if !ctx.Args().Present() { - return cli.NewExitError("missing script", 1) + return cli.Exit("missing script", 1) } - s = ctx.Args()[0] + s = ctx.Args().Slice()[0] } b, err = base64.StdEncoding.DecodeString(s) if err != nil || ctx.Bool("hex") { b, err = hex.DecodeString(s) } if err != nil { - return cli.NewExitError("unknown encoding: base64 or hex are supported", 1) + return cli.Exit("unknown encoding: base64 or hex are supported", 1) } v := vm.New() v.LoadScript(b) diff --git a/cli/util/dump.go b/cli/util/dump.go index 33ad698fe..6d4e8d38f 100644 --- a/cli/util/dump.go +++ b/cli/util/dump.go @@ -8,30 +8,30 @@ import ( "github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/cli/query" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func txDump(ctx *cli.Context) error { - args := ctx.Args() + args := ctx.Args().Slice() if len(args) == 0 { - return cli.NewExitError("missing input file", 1) + return cli.Exit("missing input file", 1) } else if len(args) > 1 { - return cli.NewExitError("only one input file is accepted", 1) + return cli.Exit("only one input file is accepted", 1) } c, err := paramcontext.Read(args[0]) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } tx, ok := c.Verifiable.(*transaction.Transaction) if !ok { - return cli.NewExitError("verifiable item is not a transaction", 1) + return cli.Exit("verifiable item is not a transaction", 1) } err = query.DumpApplicationLog(ctx, nil, tx, nil, true) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if ctx.String(options.RPCEndpointFlag) != "" { @@ -41,15 +41,15 @@ func txDump(ctx *cli.Context) error { var err error cl, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } res, err := cl.InvokeScript(tx.Script, tx.Signers) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } resS, err := json.MarshalIndent(res, "", " ") if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } fmt.Fprintln(ctx.App.Writer, string(resS)) } diff --git a/cli/util/send.go b/cli/util/send.go index a67465274..be65c7fbf 100644 --- a/cli/util/send.go +++ b/cli/util/send.go @@ -8,25 +8,25 @@ import ( "github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func sendTx(ctx *cli.Context) error { - args := ctx.Args() + args := ctx.Args().Slice() if len(args) == 0 { - return cli.NewExitError("missing input file", 1) + return cli.Exit("missing input file", 1) } else if len(args) > 1 { - return cli.NewExitError("only one input file is accepted", 1) + return cli.Exit("only one input file is accepted", 1) } pc, err := paramcontext.Read(args[0]) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } tx, err := pc.GetCompleteTransaction() if err != nil { - return cli.NewExitError(fmt.Errorf("failed to complete transaction: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to complete transaction: %w", err), 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -34,18 +34,18 @@ func sendTx(ctx *cli.Context) error { c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create RPC client: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to create RPC client: %w", err), 1) } res, err := c.SendRawTransaction(tx) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1) } var aer *state.AppExecResult if ctx.Bool("await") { version, err := c.GetVersion() aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1) + return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1) } } txctx.DumpTransactionInfo(ctx.App.Writer, res, aer) diff --git a/cli/vm/cli.go b/cli/vm/cli.go index 8389d0c84..2c742d6a8 100644 --- a/cli/vm/cli.go +++ b/cli/vm/cli.go @@ -44,7 +44,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util/slice" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) @@ -70,22 +70,22 @@ const ( ) var ( - historicFlag = cli.IntFlag{ + historicFlag = &cli.IntFlag{ Name: historicFlagFullName, Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " + "Assuming that block N-th is specified as an argument, the historic invocation is based on the storage state of height N and fake currently-accepting block with index N+1.", } - gasFlag = cli.Int64Flag{ + gasFlag = &cli.Int64Flag{ Name: gasFlagFullName, Usage: "GAS limit for this execution (integer number, satoshi).", } - hashFlag = cli.StringFlag{ + hashFlag = &flags.AddressFlag{ Name: hashFlagFullName, Usage: "Smart-contract hash in LE form or address", } ) -var commands = []cli.Command{ +var commands = []*cli.Command{ { Name: "exit", Usage: "Exit the VM prompt", @@ -339,9 +339,10 @@ Example: Usage: "Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration)", UsageText: `env [-v]`, Flags: []cli.Flag{ - cli.BoolFlag{ - Name: verboseFlagFullName + ",v", - Usage: "Print the whole blockchain node configuration.", + &cli.BoolFlag{ + Name: verboseFlagFullName, + Aliases: []string{"v"}, + Usage: "Print the whole blockchain node configuration.", }, }, Description: `Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration). @@ -353,15 +354,17 @@ Example: { Name: "storage", Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation", - UsageText: `storage [] [--backwards] [--diff]`, + UsageText: `storage [--backwards] [--diff] []`, Flags: []cli.Flag{ - cli.BoolFlag{ - Name: backwardsFlagFullName + ",b", - Usage: "Backwards traversal direction", + &cli.BoolFlag{ + Name: backwardsFlagFullName, + Aliases: []string{"b"}, + Usage: "Backwards traversal direction", }, - cli.BoolFlag{ - Name: diffFlagFullName + ",d", - Usage: "Dump only those storage items that were added or changed during the current script invocation. Note that this call won't show removed storage items, use 'changes' command for that.", + &cli.BoolFlag{ + Name: diffFlagFullName, + Aliases: []string{"d"}, + Usage: "Dump only those storage items that were added or changed during the current script invocation. Note that this call won't show removed storage items, use 'changes' command for that.", }, }, Description: `Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation. @@ -400,7 +403,7 @@ func init() { if !c.Hidden { var flagsItems []readline.PrefixCompleterInterface for _, f := range c.Flags { - names := strings.SplitN(f.GetName(), ", ", 2) // only long name will be offered + names := strings.SplitN(f.Names()[0], ", ", 2) // only long name will be offered flagsItems = append(flagsItems, readline.PcItem("--"+names[0])) } pcItems = append(pcItems, readline.PcItem(c.Name, flagsItems...)) @@ -459,7 +462,7 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg log, _, logCloser, err := options.HandleLoggingParams(false, cfg.ApplicationConfiguration) if err != nil { - return nil, cli.NewExitError(fmt.Errorf("failed to init logger: %w", err), 1) + return nil, cli.Exit(fmt.Errorf("failed to init logger: %w", err), 1) } filter := zap.WrapCore(func(z zapcore.Core) zapcore.Core { return options.NewFilteringCore(z, func(entry zapcore.Entry) bool { @@ -479,12 +482,12 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg chain, err := core.NewBlockchain(store, cfg.Blockchain(), fLog) if err != nil { - return nil, cli.NewExitError(fmt.Errorf("could not initialize blockchain: %w", err), 1) + return nil, cli.Exit(fmt.Errorf("could not initialize blockchain: %w", err), 1) } // Do not run chain, we need only state-related functionality from it. ic, err := chain.GetTestVM(trigger.Application, nil, nil) if err != nil { - return nil, cli.NewExitError(fmt.Errorf("failed to create test VM: %w", err), 1) + return nil, cli.Exit(fmt.Errorf("failed to create test VM: %w", err), 1) } vmcli := CLI{ @@ -610,7 +613,7 @@ func handleJump(c *cli.Context) error { } func getInstructionParameter(c *cli.Context) (int, error) { - args := c.Args() + args := c.Args().Slice() if len(args) != 1 { return 0, fmt.Errorf("%w: ", ErrMissingParameter) } @@ -682,15 +685,12 @@ func getHashFlag(c *cli.Context) (util.Uint160, error) { if !c.IsSet(hashFlagFullName) { return util.Uint160{}, nil } - h, err := flags.ParseAddress(c.String(hashFlagFullName)) - if err != nil { - return util.Uint160{}, fmt.Errorf("failed to parse contract hash: %w", err) - } - return h, nil + h := c.Generic(hashFlagFullName).(*flags.Address) + return h.Uint160(), nil } func handleLoadNEF(c *cli.Context) error { - args := c.Args() + args := c.Args().Slice() if len(args) < 1 { return fmt.Errorf("%w: is required", ErrMissingParameter) } @@ -734,7 +734,7 @@ func handleLoadNEF(c *cli.Context) error { } var signers []transaction.Signer if signersStartOffset != 0 && len(args) > signersStartOffset { - signers, err = cmdargs.ParseSigners(c.Args()[signersStartOffset:]) + signers, err = cmdargs.ParseSigners(args[signersStartOffset:]) if err != nil { return fmt.Errorf("%w: failed to parse signers: %w", ErrInvalidParameter, err) } @@ -761,7 +761,7 @@ func handleLoadNEF(c *cli.Context) error { } func handleLoadBase64(c *cli.Context) error { - args := c.Args() + args := c.Args().Slice() if len(args) < 1 { return fmt.Errorf("%w: ", ErrMissingParameter) } @@ -801,7 +801,7 @@ func createFakeTransaction(script []byte, signers []transaction.Signer) *transac } func handleLoadHex(c *cli.Context) error { - args := c.Args() + args := c.Args().Slice() if len(args) < 1 { return fmt.Errorf("%w: ", ErrMissingParameter) } @@ -833,7 +833,7 @@ func handleLoadHex(c *cli.Context) error { } func handleLoadGo(c *cli.Context) error { - args := c.Args() + args := c.Args().Slice() if len(args) < 1 { return fmt.Errorf("%w: ", ErrMissingParameter) } @@ -885,7 +885,7 @@ func handleLoadGo(c *cli.Context) error { } func handleLoadTx(c *cli.Context) error { - args := c.Args() + args := c.Args().Slice() if len(args) < 1 { return fmt.Errorf("%w: ", ErrMissingParameter) } @@ -933,7 +933,7 @@ func handleLoadDeployed(c *cli.Context) error { if !c.Args().Present() { return errors.New("contract hash, address or ID is mandatory argument") } - args := c.Args() + args := c.Args().Slice() hashOrID := args[0] ic := getInteropContextFromContext(c.App) h, err := flags.ParseAddress(hashOrID) @@ -1062,7 +1062,7 @@ func getManifestFromFile(name string) (*manifest.Manifest, error) { func handleRun(c *cli.Context) error { v := getVMFromContext(c.App) cs := getContractStateFromContext(c.App) - args := c.Args() + args := c.Args().Slice() if len(args) != 0 { var ( params []stackitem.Item @@ -1181,7 +1181,7 @@ func handleStep(c *cli.Context) error { return nil } v := getVMFromContext(c.App) - args := c.Args() + args := c.Args().Slice() if len(args) > 0 { n, err = strconv.Atoi(args[0]) if err != nil { @@ -1422,7 +1422,7 @@ func (c *CLI) Run() error { } func handleParse(c *cli.Context) error { - res, err := Parse(c.Args()) + res, err := Parse(c.Args().Slice()) if err != nil { return err } diff --git a/cli/vm/cli_test.go b/cli/vm/cli_test.go index 63454271a..555c66fc9 100644 --- a/cli/vm/cli_test.go +++ b/cli/vm/cli_test.go @@ -1188,7 +1188,7 @@ func TestDumpStorage(t *testing.T) { "storage "+address.Uint160ToString(h), "storage 1", "storage 1 "+hex.EncodeToString(expected[0].Key), - "storage 1 --backwards", + "storage --backwards 1", "exit", ) e.checkStorage(t, expected...) @@ -1214,11 +1214,11 @@ func TestDumpStorageDiff(t *testing.T) { diff := storage.KeyValue{Key: []byte{3}, Value: []byte{3}} e.runProg(t, "storage 1", - "storage 1 --diff", + "storage --diff 1", "loadhex "+hex.EncodeToString(script.Bytes()), "run", "storage 1", - "storage 1 --diff", + "storage --diff 1", "exit", ) diff --git a/cli/vm/vm.go b/cli/vm/vm.go index e9e1e2a58..49eb932cf 100644 --- a/cli/vm/vm.go +++ b/cli/vm/vm.go @@ -8,16 +8,16 @@ import ( "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) // NewCommands returns 'vm' command. -func NewCommands() []cli.Command { +func NewCommands() []*cli.Command { cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath} cfgFlags = append(cfgFlags, options.Network...) - return []cli.Command{{ + return []*cli.Command{{ Name: "vm", - Usage: "start the virtual machine", + Usage: "Start the virtual machine", Action: startVMPrompt, Flags: cfgFlags, }} @@ -30,7 +30,7 @@ func startVMPrompt(ctx *cli.Context) error { cfg, err := options.GetConfigFromContext(ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if ctx.NumFlags() == 0 { cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.InMemoryDB @@ -42,7 +42,7 @@ func startVMPrompt(ctx *cli.Context) error { p, err := NewWithConfig(true, os.Exit, &readline.Config{}, cfg) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create VM CLI: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to create VM CLI: %w", err), 1) } return p.Run() } 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.go b/cli/wallet/multisig.go index f6d600da9..9c3f2ed6d 100644 --- a/cli/wallet/multisig.go +++ b/cli/wallet/multisig.go @@ -12,7 +12,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) func signStoredTransaction(ctx *cli.Context) error { @@ -28,52 +28,52 @@ func signStoredTransaction(ctx *cli.Context) error { pc, err := paramcontext.Read(ctx.String("in")) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } if !addrFlag.IsSet { - return cli.NewExitError("address was not provided", 1) + return cli.Exit("address was not provided", 1) } acc, _, err := options.GetAccFromContext(ctx) if err != nil { - return cli.NewExitError(err, 1) + return cli.Exit(err, 1) } tx, ok := pc.Verifiable.(*transaction.Transaction) if !ok { - return cli.NewExitError("verifiable item is not a transaction", 1) + return cli.Exit("verifiable item is not a transaction", 1) } if !tx.HasSigner(acc.ScriptHash()) { - return cli.NewExitError("tx signers don't contain provided account", 1) + return cli.Exit("tx signers don't contain provided account", 1) } if acc.CanSign() { sign := acc.SignHashable(pc.Network, pc.Verifiable) if err := pc.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil { - return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1) + return cli.Exit(fmt.Errorf("can't add signature: %w", err), 1) } } else if rpcNode == "" { - return cli.NewExitError(fmt.Errorf("can't sign transactions with the given account and no RPC endpoing given to send anything signed"), 1) + return cli.Exit(fmt.Errorf("can't sign transactions with the given account and no RPC endpoing given to send anything signed"), 1) } // Not saving and not sending, print. if out == "" && rpcNode == "" { txt, err := json.MarshalIndent(pc, " ", " ") if err != nil { - return cli.NewExitError(fmt.Errorf("can't display resulting context: %w", err), 1) + return cli.Exit(fmt.Errorf("can't display resulting context: %w", err), 1) } fmt.Fprintln(ctx.App.Writer, string(txt)) return nil } if out != "" { if err := paramcontext.Save(pc, out); err != nil { - return cli.NewExitError(fmt.Errorf("can't save resulting context: %w", err), 1) + return cli.Exit(fmt.Errorf("can't save resulting context: %w", err), 1) } } if rpcNode != "" { tx, err = pc.GetCompleteTransaction() if err != nil { - return cli.NewExitError(fmt.Errorf("failed to complete transaction: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to complete transaction: %w", err), 1) } gctx, cancel := options.GetTimeoutContext(ctx) @@ -82,17 +82,17 @@ func signStoredTransaction(ctx *cli.Context) error { var err error // `GetRPCClient` returns specialized type. c, err := options.GetRPCClient(gctx, ctx) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create RPC client: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to create RPC client: %w", err), 1) } res, err := c.SendRawTransaction(tx) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1) + return cli.Exit(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1) } if ctx.Bool("await") { version, err := c.GetVersion() aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1) + return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1) } } } 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 9fcce1a86..7c4d97206 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -19,20 +19,22 @@ import ( "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) -func newNEP11Commands() []cli.Command { +func newNEP11Commands() []*cli.Command { maxIters := strconv.Itoa(config.DefaultMaxIteratorResultItems) - tokenAddressFlag := flags.AddressFlag{ - Name: "token", - Usage: "Token contract address or hash in LE", + tokenAddressFlag := &flags.AddressFlag{ + 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", + ownerAddressFlag := &flags.AddressFlag{ + Name: "address", + Usage: "NFT owner address or hash in LE", + Required: true, } - tokenID := cli.StringFlag{ + tokenID := &cli.StringFlag{ Name: "id", Usage: "Hex-encoded token ID", } @@ -45,10 +47,10 @@ func newNEP11Commands() []cli.Command { copy(transferFlags, baseTransferFlags) transferFlags = append(transferFlags, tokenID) transferFlags = append(transferFlags, options.RPC...) - return []cli.Command{ + return []*cli.Command{ { Name: "balance", - Usage: "get address balance", + Usage: "Get address balance", UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint [--timeout