Merge pull request #3495 from nspcc-dev/cli

cli: upgrade urfave lib to v2
This commit is contained in:
Anna Shaleva 2024-07-12 12:49:50 +03:00 committed by GitHub
commit c65d9f40e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1229 additions and 963 deletions

View file

@ -12,7 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/vm" "github.com/nspcc-dev/neo-go/cli/vm"
"github.com/nspcc-dev/neo-go/cli/wallet" "github.com/nspcc-dev/neo-go/cli/wallet"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func versionPrinter(c *cli.Context) { func versionPrinter(c *cli.Context) {

View file

@ -12,7 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
@ -138,16 +138,16 @@ const (
// GetSignersFromContext returns signers parsed from context args starting // GetSignersFromContext returns signers parsed from context args starting
// from the specified offset. // 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() args := ctx.Args()
var ( var (
signers []transaction.Signer signers []transaction.Signer
err error err error
) )
if args.Present() && len(args) > offset { if args.Present() && args.Len() > offset {
signers, err = ParseSigners(args[offset:]) signers, err = ParseSigners(args.Slice()[offset:])
if err != nil { if err != nil {
return nil, cli.NewExitError(err, 1) return nil, cli.Exit(err, 1)
} }
} }
return signers, nil return signers, nil
@ -230,7 +230,7 @@ func parseCosigner(c string) (transaction.Signer, error) {
} }
// GetDataFromContext returns data parameter from context args. // 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 ( var (
data any data any
offset int offset int
@ -239,17 +239,17 @@ func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) {
) )
args := ctx.Args() args := ctx.Args()
if args.Present() { if args.Present() {
offset, params, err = ParseParams(args, true) offset, params, err = ParseParams(args.Slice(), true)
if err != nil { 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 { 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 { if len(params) != 0 {
data, err = smartcontract.ExpandParameterToEmitable(params[0]) data, err = smartcontract.ExpandParameterToEmitable(params[0])
if err != nil { 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. // 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. // 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() { 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 return nil
} }
@ -348,3 +348,14 @@ func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers
} }
return signersAccounts, nil 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
}
}

View file

@ -7,7 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util" "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. // Address is a wrapper for a Uint160 with flag.Value methods.
@ -16,11 +16,15 @@ type Address struct {
Value util.Uint160 Value util.Uint160
} }
// AddressFlag is a flag with type string. // AddressFlag is a flag with type Uint160.
type AddressFlag struct { type AddressFlag struct {
Name string Name string
Usage string Usage string
Value Address Value Address
Aliases []string
Required bool
Hidden bool
Action func(*cli.Context, string) error
} }
var ( var (
@ -37,7 +41,7 @@ func (a Address) String() string {
func (a *Address) Set(s string) error { func (a *Address) Set(s string) error {
addr, err := ParseAddress(s) addr, err := ParseAddress(s)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
a.IsSet = true a.IsSet = true
a.Value = addr a.Value = addr
@ -63,9 +67,9 @@ func (f AddressFlag) IsSet() bool {
// (for usage defaults). // (for usage defaults).
func (f AddressFlag) String() string { func (f AddressFlag) String() string {
var names []string var names []string
eachName(f.Name, func(name string) { for _, name := range f.Names() {
names = append(names, getNameHelp(name)) names = append(names, getNameHelp(name))
}) }
return strings.Join(names, ", ") + "\t" + f.Usage return strings.Join(names, ", ") + "\t" + f.Usage
} }
@ -77,17 +81,57 @@ func getNameHelp(name string) string {
return fmt.Sprintf("--%s value", name) return fmt.Sprintf("--%s value", name)
} }
// GetName returns the name of the flag. // Names returns the names of the flag.
func (f AddressFlag) GetName() string { func (f AddressFlag) Names() []string {
return f.Name 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. // Apply populates the flag given the flag set and environment.
// Ignores errors. // Ignores errors.
func (f AddressFlag) Apply(set *flag.FlagSet) { func (f AddressFlag) Apply(set *flag.FlagSet) error {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
set.Var(&f.Value, name, f.Usage) 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 flags 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. // ParseAddress parses a Uint160 from either an LE string or an address.

View file

@ -9,6 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
) )
func TestParseAddress(t *testing.T) { func TestParseAddress(t *testing.T) {
@ -109,22 +110,102 @@ func TestAddress_getNameHelp(t *testing.T) {
require.Equal(t, "--flag value", getNameHelp("flag")) require.Equal(t, "--flag value", getNameHelp("flag"))
} }
func TestAddressFlag_GetName(t *testing.T) { func TestAddressFlag_Names(t *testing.T) {
flag := AddressFlag{ 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) { func TestAddress(t *testing.T) {
f := flag.NewFlagSet("", flag.ContinueOnError) f := flag.NewFlagSet("", flag.ContinueOnError)
f.SetOutput(io.Discard) // don't pollute test output f.SetOutput(io.Discard) // don't pollute test output
addr := AddressFlag{Name: "addr, a"} addr := AddressFlag{Name: "addr", Aliases: []string{"a"}}
addr.Apply(f) err := addr.Apply(f)
require.NoError(t, err)
require.NoError(t, f.Parse([]string{"--addr", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"})) require.NoError(t, f.Parse([]string{"--addr", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"}))
require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String()) require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String())
require.NoError(t, f.Parse([]string{"-a", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"})) require.NoError(t, f.Parse([]string{"-a", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"}))
require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String()) require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String())
require.Error(t, f.Parse([]string{"--addr", "kek"})) 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)
}

View file

@ -5,7 +5,7 @@ import (
"strings" "strings"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "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. // Fixed8 is a wrapper for a Uint160 with flag.Value methods.
@ -18,6 +18,10 @@ type Fixed8Flag struct {
Name string Name string
Usage string Usage string
Value Fixed8 Value Fixed8
Aliases []string
Required bool
Hidden bool
Action func(*cli.Context, string) error
} }
var ( var (
@ -34,7 +38,7 @@ func (a Fixed8) String() string {
func (a *Fixed8) Set(s string) error { func (a *Fixed8) Set(s string) error {
f, err := fixedn.Fixed8FromString(s) f, err := fixedn.Fixed8FromString(s)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
a.Value = f a.Value = f
return nil return nil
@ -45,31 +49,75 @@ func (a *Fixed8) Fixed8() fixedn.Fixed8 {
return a.Value 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 // String returns a readable representation of this value
// (for usage defaults). // (for usage defaults).
func (f Fixed8Flag) String() string { func (f Fixed8Flag) String() string {
var names []string var names []string
eachName(f.Name, func(name string) { for _, name := range f.Names() {
names = append(names, getNameHelp(name)) names = append(names, getNameHelp(name))
}) }
return strings.Join(names, ", ") + "\t" + f.Usage return strings.Join(names, ", ") + "\t" + f.Usage
} }
// GetName returns the name of the flag. // Names returns the names of the flag.
func (f Fixed8Flag) GetName() string { func (f Fixed8Flag) Names() []string {
return f.Name 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. // Apply populates the flag given the flag set and environment.
// Ignores errors. // Ignores errors.
func (f Fixed8Flag) Apply(set *flag.FlagSet) { func (f Fixed8Flag) Apply(set *flag.FlagSet) error {
eachName(f.Name, func(name string) { for _, name := range f.Names() {
set.Var(&f.Value, name, f.Usage) set.Var(&f.Value, name, f.Usage)
}) }
return nil
} }
// Fixed8FromContext returns a parsed util.Fixed8 value provided flag name. // Fixed8FromContext returns a parsed util.Fixed8 value provided flag name.
func Fixed8FromContext(ctx *cli.Context, name string) fixedn.Fixed8 { func Fixed8FromContext(ctx *cli.Context, name string) fixedn.Fixed8 {
return ctx.Generic(name).(*Fixed8).Value 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 flags value in the given Context.
func (f Fixed8Flag) Get(ctx *cli.Context) Fixed8 {
adr := ctx.Generic(f.Name).(*Fixed8)
return *adr
}

View file

@ -7,6 +7,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
) )
func TestFixed8_String(t *testing.T) { 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()) require.Equal(t, "--myFlag value\tGas amount", flag.String())
} }
func TestFixed8Flag_GetName(t *testing.T) { func TestFixed8Flag_Names(t *testing.T) {
flag := Fixed8Flag{ flag := Fixed8Flag{
Name: "myFlag", Name: "myFlag",
} }
require.Equal(t, "myFlag", flag.GetName()) require.Equal(t, []string{"myFlag"}, flag.Names())
} }
func TestFixed8(t *testing.T) { func TestFixed8(t *testing.T) {
f := flag.NewFlagSet("", flag.ContinueOnError) f := flag.NewFlagSet("", flag.ContinueOnError)
f.SetOutput(io.Discard) // don't pollute test output f.SetOutput(io.Discard) // don't pollute test output
gas := Fixed8Flag{Name: "gas, g"} gas := Fixed8Flag{Name: "gas", Aliases: []string{"g"}, Usage: "Gas amount", Value: Fixed8{Value: 0}, Required: true, Hidden: false, Action: nil}
gas.Apply(f) err := gas.Apply(f)
require.NoError(t, err)
require.NoError(t, f.Parse([]string{"--gas", "0.123"})) require.NoError(t, f.Parse([]string{"--gas", "0.123"}))
require.Equal(t, "0.123", f.Lookup("g").Value.String()) require.Equal(t, "0.123", f.Lookup("g").Value.String())
require.NoError(t, f.Parse([]string{"-g", "0.456"})) require.NoError(t, f.Parse([]string{"-g", "0.456"}))
require.Equal(t, "0.456", f.Lookup("g").Value.String()) require.Equal(t, "0.456", f.Lookup("g").Value.String())
require.Error(t, f.Parse([]string{"--gas", "kek"})) 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())
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"fmt"
"os" "os"
"github.com/nspcc-dev/neo-go/cli/app" "github.com/nspcc-dev/neo-go/cli/app"
@ -10,6 +11,7 @@ func main() {
ctl := app.New() ctl := app.New()
if err := ctl.Run(os.Args); err != nil { if err := ctl.Run(os.Args); err != nil {
panic(err) fmt.Fprintln(ctl.ErrWriter, err)
os.Exit(1)
} }
} }

View file

@ -55,11 +55,14 @@ func TestNEP11Import(t *testing.T) {
"--wallet", walletPath, "--wallet", walletPath,
} }
// missing token hash // missing token hash
e.RunWithError(t, args...) e.RunWithErrorCheck(t, `Required flag "token" not set`, args...)
// excessive parameters // excessive parameters
e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE(), "something")...) 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 // good: non-divisible
e.Run(t, append(args, "--token", nnsContractHash.StringLE())...) 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", cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOf",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
// ownerOf: missing token ID // ownerOf: missing token ID
@ -244,11 +247,11 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
// tokensOf: missing owner address // tokensOf: missing owner address
e.RunWithError(t, cmdTokensOf...) e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...)
cmdTokensOf = append(cmdTokensOf, "--address", nftOwnerAddr) cmdTokensOf = append(cmdTokensOf, "--address", nftOwnerAddr)
// tokensOf: good // tokensOf: good
@ -260,7 +263,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
"neo-go", "wallet", "nep11", "properties", "neo-go", "wallet", "nep11", "properties",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdProperties = append(cmdProperties, "--token", h.StringLE())
// properties: no token ID // properties: no token ID
@ -286,7 +289,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdTokens = append(cmdTokens, "--token", h.StringLE())
// tokens: excessive parameters // tokens: excessive parameters
@ -514,7 +517,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD", cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
// ownerOfD: missing token ID // ownerOfD: missing token ID
@ -529,11 +532,11 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf", cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
// tokensOf: missing owner address // tokensOf: missing owner address
e.RunWithError(t, cmdTokensOf...) e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...)
cmdTokensOf = append(cmdTokensOf, "--address", testcli.ValidatorAddr) cmdTokensOf = append(cmdTokensOf, "--address", testcli.ValidatorAddr)
// tokensOf: good // tokensOf: good
@ -547,7 +550,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
"neo-go", "wallet", "nep11", "properties", "neo-go", "wallet", "nep11", "properties",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdProperties = append(cmdProperties, "--token", h.StringLE())
// properties: no token ID // properties: no token ID
@ -580,7 +583,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens", cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--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()) cmdTokens = append(cmdTokens, "--token", h.StringLE())
// tokens: good, several tokens // tokens: good, several tokens

View file

@ -21,14 +21,13 @@ func TestNEP17Balance(t *testing.T) {
e := testcli.NewExecutor(t, true) e := testcli.NewExecutor(t, true)
args := []string{ args := []string{
"neo-go", "wallet", "nep17", "multitransfer", "neo-go", "wallet", "nep17", "multitransfer", "--force",
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0], "--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
"--wallet", testcli.ValidatorWallet, "--wallet", testcli.ValidatorWallet,
"--from", testcli.ValidatorAddr, "--from", testcli.ValidatorAddr,
"GAS:" + testcli.TestWalletMultiAccount1 + ":1", "GAS:" + testcli.TestWalletMultiAccount1 + ":1",
"NEO:" + testcli.TestWalletMultiAccount1 + ":10", "NEO:" + testcli.TestWalletMultiAccount1 + ":10",
"GAS:" + testcli.TestWalletMultiAccount3 + ":3", "GAS:" + testcli.TestWalletMultiAccount3 + ":3",
"--force",
} }
e.In.WriteString("one\r") e.In.WriteString("one\r")
e.Run(t, args...) e.Run(t, args...)
@ -114,7 +113,7 @@ func TestNEP17Balance(t *testing.T) {
e.CheckEOF(t) e.CheckEOF(t)
}) })
t.Run("Bad wallet", func(t *testing.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([]string{}, args[:8]...)
as = append(as, args[10:]...) as = append(as, args[10:]...)
e.In.WriteString("one\r") e.In.WriteString("one\r")
e.RunWithError(t, as...) e.RunWithErrorCheck(t, `Required flag "to" not set`, as...)
e.In.Reset() e.In.Reset()
}) })
@ -327,7 +326,7 @@ func TestNEP17ImportToken(t *testing.T) {
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
// missing token hash // 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], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
"--wallet", walletPath) "--wallet", walletPath)

View file

@ -8,7 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/internal/testcli" "github.com/nspcc-dev/neo-go/internal/testcli"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func TestGetRPCClient(t *testing.T) { func TestGetRPCClient(t *testing.T) {

View file

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/nspcc-dev/neo-go/cli/cmdargs"
"github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/flags"
"github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/input"
"github.com/nspcc-dev/neo-go/pkg/config" "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/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "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"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"golang.org/x/term" "golang.org/x/term"
@ -47,69 +48,92 @@ const (
const RPCEndpointFlag = "rpc-endpoint" const RPCEndpointFlag = "rpc-endpoint"
// Wallet is a set of flags used for wallet operations. // Wallet is a set of flags used for wallet operations.
var Wallet = []cli.Flag{cli.StringFlag{ var Wallet = []cli.Flag{
Name: "wallet, w", &cli.StringFlag{
Usage: "wallet to use to get the key for transaction signing; conflicts with --wallet-config flag", Name: "wallet",
}, cli.StringFlag{ Aliases: []string{"w"},
Usage: "Wallet to use to get the key for transaction signing; conflicts with --wallet-config flag",
},
&cli.StringFlag{
Name: "wallet-config", Name: "wallet-config",
Usage: "path to wallet config to use to get the key for transaction signing; conflicts with --wallet flag"}, 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 // Network is a set of flags for choosing the network to operate on
// (privnet/mainnet/testnet). // (privnet/mainnet/testnet).
var Network = []cli.Flag{ var Network = []cli.Flag{
cli.BoolFlag{Name: "privnet, p", Usage: "use private network configuration (if --config-file option is not specified)"}, &cli.BoolFlag{
cli.BoolFlag{Name: "mainnet, m", Usage: "use mainnet network configuration (if --config-file option is not specified)"}, Name: "privnet",
cli.BoolFlag{Name: "testnet, t", Usage: "use testnet network configuration (if --config-file option is not specified)"}, Aliases: []string{"p"},
cli.BoolFlag{Name: "unittest", Hidden: true}, 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). // RPC is a set of flags used for RPC connections (endpoint and timeout).
var RPC = []cli.Flag{ var RPC = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: RPCEndpointFlag + ", r", Name: RPCEndpointFlag,
Aliases: []string{"r"},
Usage: "RPC node address", Usage: "RPC node address",
Required: true,
Action: cmdargs.EnsureNotEmpty("rpc-endpoint"),
}, },
cli.DurationFlag{ &cli.DurationFlag{
Name: "timeout, s", Name: "timeout",
Aliases: []string{"s"},
Value: DefaultTimeout, Value: DefaultTimeout,
Usage: "Timeout for the operation", Usage: "Timeout for the operation",
}, },
} }
// Historic is a flag for commands that can perform historic invocations. // Historic is a flag for commands that can perform historic invocations.
var Historic = cli.StringFlag{ var Historic = &cli.StringFlag{
Name: "historic", Name: "historic",
Usage: "Use historic state (height, block hash or state root hash)", Usage: "Use historic state (height, block hash or state root hash)",
} }
// Config is a flag for commands that use node configuration. // Config is a flag for commands that use node configuration.
var Config = cli.StringFlag{ var Config = &cli.StringFlag{
Name: "config-path", 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 // ConfigFile is a flag for commands that use node configuration and provide
// path to the specific config file instead of config path. // path to the specific config file instead of config path.
var ConfigFile = cli.StringFlag{ var ConfigFile = &cli.StringFlag{
Name: "config-file", 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 // RelativePath is a flag for commands that use node configuration and provide
// a prefix to all relative paths in config files. // a prefix to all relative paths in config files.
var RelativePath = cli.StringFlag{ var RelativePath = &cli.StringFlag{
Name: "relative-path", 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. // Debug is a flag for commands that allow node in debug mode usage.
var Debug = cli.BoolFlag{ var Debug = &cli.BoolFlag{
Name: "debug, d", Name: "debug",
Usage: "enable debug logging (LOTS of output, overrides configuration)", 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 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 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") 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. // GetRPCClient returns an RPC client instance for the given Context.
func GetRPCClient(gctx context.Context, ctx *cli.Context) (*rpcclient.Client, cli.ExitCoder) { func GetRPCClient(gctx context.Context, ctx *cli.Context) (*rpcclient.Client, cli.ExitCoder) {
endpoint := ctx.String(RPCEndpointFlag) endpoint := ctx.String(RPCEndpointFlag)
if len(endpoint) == 0 {
return nil, cli.NewExitError(errNoEndpoint, 1)
}
c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{}) c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{})
if err != nil { if err != nil {
return nil, cli.NewExitError(err, 1) return nil, cli.Exit(err, 1)
} }
err = c.Init() err = c.Init()
if err != nil { if err != nil {
return nil, cli.NewExitError(err, 1) return nil, cli.Exit(err, 1)
} }
return c, nil 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. // Might as well be a block hash, but it makes no practical difference.
return invoker.NewHistoricWithState(u256, c, signers), nil 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 // 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) a, actorErr := actor.New(c, signers)
if actorErr != nil { if actorErr != nil {
c.Close() 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 return c, a, nil
} }

View file

@ -8,7 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func TestGetNetwork(t *testing.T) { func TestGetNetwork(t *testing.T) {

View file

@ -22,21 +22,22 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate" "github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// NewCommands returns 'query' command. // NewCommands returns 'query' command.
func NewCommands() []cli.Command { func NewCommands() []*cli.Command {
queryTxFlags := append([]cli.Flag{ queryTxFlags := append([]cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "verbose, v", Name: "verbose",
Aliases: []string{"v"},
Usage: "Output full tx info and execution logs", Usage: "Output full tx info and execution logs",
}, },
}, options.RPC...) }, options.RPC...)
return []cli.Command{{ return []*cli.Command{{
Name: "query", Name: "query",
Usage: "Query data from RPC node", Usage: "Query data from RPC node",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "candidates", Name: "candidates",
Usage: "Get candidates and votes", Usage: "Get candidates and votes",
@ -61,14 +62,14 @@ func NewCommands() []cli.Command {
{ {
Name: "tx", Name: "tx",
Usage: "Query transaction status", Usage: "Query transaction status",
UsageText: "neo-go query tx <hash> -r endpoint [-s timeout] [-v]", UsageText: "neo-go query tx -r endpoint [-s timeout] [-v] <hash>",
Action: queryTx, Action: queryTx,
Flags: queryTxFlags, Flags: queryTxFlags,
}, },
{ {
Name: "voter", Name: "voter",
Usage: "Print NEO holder account state", Usage: "Print NEO holder account state",
UsageText: "neo-go query voter <address> -r endpoint [-s timeout]", UsageText: "neo-go query voter -r endpoint [-s timeout] <address>",
Action: queryVoter, Action: queryVoter,
Flags: options.RPC, Flags: options.RPC,
}, },
@ -77,16 +78,16 @@ func NewCommands() []cli.Command {
} }
func queryTx(ctx *cli.Context) error { func queryTx(ctx *cli.Context) error {
args := ctx.Args() args := ctx.Args().Slice()
if len(args) == 0 { 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 { } 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")) txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
if err != nil { 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) gctx, cancel := options.GetTimeoutContext(ctx)
@ -94,25 +95,25 @@ func queryTx(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
txOut, err := c.GetRawTransactionVerbose(txHash) txOut, err := c.GetRawTransactionVerbose(txHash)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
var res *result.ApplicationLog var res *result.ApplicationLog
if !txOut.Blockhash.Equals(util.Uint256{}) { if !txOut.Blockhash.Equals(util.Uint256{}) {
res, err = c.GetApplicationLog(txHash, nil) res, err = c.GetApplicationLog(txHash, nil)
if err != 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")) err = DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
} }
@ -179,16 +180,16 @@ func queryCandidates(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
vals, err := c.GetCandidates() vals, err := c.GetCandidates()
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
comm, err := c.GetCommittee() comm, err := c.GetCommittee()
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
sort.Slice(vals, func(i, j int) bool { sort.Slice(vals, func(i, j int) bool {
@ -225,12 +226,12 @@ func queryCommittee(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
comm, err := c.GetCommittee() comm, err := c.GetCommittee()
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
for _, k := range comm { for _, k := range comm {
@ -251,12 +252,12 @@ func queryHeight(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
blockCount, err := c.GetBlockCount() blockCount, err := c.GetBlockCount()
if err != nil { 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. 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 { func queryVoter(ctx *cli.Context) error {
args := ctx.Args() args := ctx.Args().Slice()
if len(args) == 0 { if len(args) == 0 {
return cli.NewExitError("No address specified", 1) return cli.Exit("no address specified", 1)
} else if len(args) > 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]) addr, err := flags.ParseAddress(args[0])
if err != nil { 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) gctx, cancel := options.GetTimeoutContext(ctx)
@ -294,14 +295,14 @@ func queryVoter(ctx *cli.Context) error {
st, err := neoToken.GetAccountState(addr) st, err := neoToken.GetAccountState(addr)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if st == nil { if st == nil {
st = new(state.NEOBalance) st = new(state.NEOBalance)
} }
dec, err := neoToken.Decimals() dec, err := neoToken.Decimals()
if err != nil { 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" voted := "null"
if st.VoteTo != nil { if st.VoteTo != nil {

View file

@ -27,13 +27,13 @@ import (
"github.com/nspcc-dev/neo-go/pkg/services/oracle" "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/rpcsrv"
"github.com/nspcc-dev/neo-go/pkg/services/stateroot" "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"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
// NewCommands returns 'node' command. // NewCommands returns 'node' command.
func NewCommands() []cli.Command { func NewCommands() []*cli.Command {
cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath} cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath}
cfgFlags = append(cfgFlags, options.Network...) cfgFlags = append(cfgFlags, options.Network...)
var cfgWithCountFlags = make([]cli.Flag, len(cfgFlags)) var cfgWithCountFlags = make([]cli.Flag, len(cfgFlags))
@ -41,75 +41,80 @@ func NewCommands() []cli.Command {
cfgFlags = append(cfgFlags, options.Debug) cfgFlags = append(cfgFlags, options.Debug)
cfgWithCountFlags = append(cfgWithCountFlags, cfgWithCountFlags = append(cfgWithCountFlags,
cli.UintFlag{ &cli.UintFlag{
Name: "count, c", Name: "count",
Usage: "number of blocks to be processed (default or 0: all chain)", Aliases: []string{"c"},
Usage: "Number of blocks to be processed (default or 0: all chain)",
}, },
) )
var cfgCountOutFlags = make([]cli.Flag, len(cfgWithCountFlags)) var cfgCountOutFlags = make([]cli.Flag, len(cfgWithCountFlags))
copy(cfgCountOutFlags, cfgWithCountFlags) copy(cfgCountOutFlags, cfgWithCountFlags)
cfgCountOutFlags = append(cfgCountOutFlags, cfgCountOutFlags = append(cfgCountOutFlags,
cli.UintFlag{ &cli.UintFlag{
Name: "start, s", Name: "start",
Usage: "block number to start from (default: 0)", Aliases: []string{"s"},
Usage: "Block number to start from",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "out, o", Name: "out",
Aliases: []string{"o"},
Usage: "Output file (stdout if not given)", Usage: "Output file (stdout if not given)",
}, },
) )
var cfgCountInFlags = make([]cli.Flag, len(cfgWithCountFlags)) var cfgCountInFlags = make([]cli.Flag, len(cfgWithCountFlags))
copy(cfgCountInFlags, cfgWithCountFlags) copy(cfgCountInFlags, cfgWithCountFlags)
cfgCountInFlags = append(cfgCountInFlags, cfgCountInFlags = append(cfgCountInFlags,
cli.StringFlag{ &cli.StringFlag{
Name: "in, i", Name: "in",
Aliases: []string{"i"},
Usage: "Input file (stdin if not given)", Usage: "Input file (stdin if not given)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "dump", Name: "dump",
Usage: "directory for storing JSON dumps", Usage: "Directory for storing JSON dumps",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "incremental, n", Name: "incremental",
Usage: "use if dump is incremental", Aliases: []string{"n"},
Usage: "Use if dump is incremental",
}, },
) )
var cfgHeightFlags = make([]cli.Flag, len(cfgFlags)+1) var cfgHeightFlags = make([]cli.Flag, len(cfgFlags)+1)
copy(cfgHeightFlags, cfgFlags) copy(cfgHeightFlags, cfgFlags)
cfgHeightFlags[len(cfgHeightFlags)-1] = cli.UintFlag{ cfgHeightFlags[len(cfgHeightFlags)-1] = &cli.UintFlag{
Name: "height", Name: "height",
Usage: "Height of the state to reset DB to", Usage: "Height of the state to reset DB to",
Required: true, Required: true,
} }
return []cli.Command{ return []*cli.Command{
{ {
Name: "node", 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]", UsageText: "neo-go node [--config-path path] [-d] [-p/-m/-t] [--config-file file]",
Action: startServer, Action: startServer,
Flags: cfgFlags, Flags: cfgFlags,
}, },
{ {
Name: "db", Name: "db",
Usage: "database manipulations", Usage: "Database manipulations",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "dump", Name: "dump",
Usage: "dump blocks (starting with block #1) to the 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]", UsageText: "neo-go db dump [-o file] [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
Action: dumpDB, Action: dumpDB,
Flags: cfgCountOutFlags, Flags: cfgCountOutFlags,
}, },
{ {
Name: "restore", Name: "restore",
Usage: "restore blocks from the 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]", UsageText: "neo-go db restore [-i file] [--dump] [-n] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
Action: restoreDB, Action: restoreDB,
Flags: cfgCountInFlags, Flags: cfgCountInFlags,
}, },
{ {
Name: "reset", 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]", UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t] [--config-file file]",
Action: resetDB, Action: resetDB,
Flags: cfgHeightFlags, 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) { func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *metrics.Service, *metrics.Service, error) {
chain, _, err := initBlockChain(cfg, log) chain, _, err := initBlockChain(cfg, log)
if err != nil { 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) prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log)
pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, 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() go chain.Run()
err = prometheus.Start() err = prometheus.Start()
if err != nil { 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() err = pprof.Start()
if err != nil { 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 return chain, prometheus, pprof, nil
@ -158,11 +163,11 @@ func dumpDB(ctx *cli.Context) error {
} }
cfg, err := options.GetConfigFromContext(ctx) cfg, err := options.GetConfigFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if logCloser != nil { if logCloser != nil {
defer func() { _ = logCloser() }() defer func() { _ = logCloser() }()
@ -174,7 +179,7 @@ func dumpDB(ctx *cli.Context) error {
if out := ctx.String("out"); out != "" { if out := ctx.String("out"); out != "" {
outStream, err = os.Create(out) outStream, err = os.Create(out)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
} }
defer outStream.Close() defer outStream.Close()
@ -192,7 +197,7 @@ func dumpDB(ctx *cli.Context) error {
chainCount := chain.BlockHeight() + 1 chainCount := chain.BlockHeight() + 1
if start+count > chainCount { 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 { if count == 0 {
count = chainCount - start count = chainCount - start
@ -203,7 +208,7 @@ func dumpDB(ctx *cli.Context) error {
writer.WriteU32LE(count) writer.WriteU32LE(count)
err = chaindump.Dump(chain, writer, start, count) err = chaindump.Dump(chain, writer, start, count)
if err != nil { if err != nil {
return cli.NewExitError(err.Error(), 1) return cli.Exit(err.Error(), 1)
} }
return nil return nil
} }
@ -218,7 +223,7 @@ func restoreDB(ctx *cli.Context) error {
} }
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if logCloser != nil { if logCloser != nil {
defer func() { _ = logCloser() }() defer func() { _ = logCloser() }()
@ -229,7 +234,7 @@ func restoreDB(ctx *cli.Context) error {
if in := ctx.String("in"); in != "" { if in := ctx.String("in"); in != "" {
inStream, err = os.Open(in) inStream, err = os.Open(in)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
} }
defer inStream.Close() defer inStream.Close()
@ -254,7 +259,7 @@ func restoreDB(ctx *cli.Context) error {
if ctx.Bool("incremental") { if ctx.Bool("incremental") {
start = reader.ReadU32LE() start = reader.ReadU32LE()
if chain.BlockHeight()+1 < start { 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) chain.BlockHeight()+1, start), 1)
} }
} }
@ -266,10 +271,10 @@ func restoreDB(ctx *cli.Context) error {
var allBlocks = reader.ReadU32LE() var allBlocks = reader.ReadU32LE()
if reader.Err != nil { if reader.Err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if skip+count > allBlocks { 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 { if count == 0 {
count = allBlocks - skip count = allBlocks - skip
@ -320,7 +325,7 @@ func restoreDB(ctx *cli.Context) error {
err = chaindump.Restore(chain, reader, skip, count, f) err = chaindump.Restore(chain, reader, skip, count, f)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
} }
@ -331,29 +336,29 @@ func resetDB(ctx *cli.Context) error {
} }
cfg, err := options.GetConfigFromContext(ctx) cfg, err := options.GetConfigFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
h := uint32(ctx.Uint("height")) h := uint32(ctx.Uint("height"))
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if logCloser != nil { if logCloser != nil {
defer func() { _ = logCloser() }() defer func() { _ = logCloser() }()
} }
chain, store, err := initBlockChain(cfg, log) chain, store, err := initBlockChain(cfg, log)
if err != nil { 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) err = chain.Reset(h)
if err != nil { 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() err = store.Close()
if err != nil { 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 return nil
} }
@ -442,12 +447,12 @@ func startServer(ctx *cli.Context) error {
cfg, err := options.GetConfigFromContext(ctx) cfg, err := options.GetConfigFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
var logDebug = ctx.Bool("debug") var logDebug = ctx.Bool("debug")
log, logLevel, logCloser, err := options.HandleLoggingParams(logDebug, cfg.ApplicationConfiguration) log, logLevel, logCloser, err := options.HandleLoggingParams(logDebug, cfg.ApplicationConfiguration)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if logCloser != nil { if logCloser != nil {
defer func() { _ = logCloser() }() defer func() { _ = logCloser() }()
@ -458,12 +463,12 @@ func startServer(ctx *cli.Context) error {
serverConfig, err := network.NewServerConfig(cfg) serverConfig, err := network.NewServerConfig(cfg)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log) chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer func() { defer func() {
pprof.ShutDown() pprof.ShutDown()
@ -473,26 +478,26 @@ func startServer(ctx *cli.Context) error {
serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log) serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log)
if err != nil { 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. srMod := chain.GetStateModule().(*corestate.Module) // Take full responsibility here.
sr, err := stateroot.New(serverConfig.StateRootCfg, srMod, log, chain, serv.BroadcastExtensible) sr, err := stateroot.New(serverConfig.StateRootCfg, srMod, log, chain, serv.BroadcastExtensible)
if err != nil { 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) serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload)
oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log) oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log) dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log) p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
errChan := make(chan error) errChan := make(chan error)
rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan) rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
@ -640,7 +645,7 @@ Main:
} }
if shutdownErr != nil { if shutdownErr != nil {
return cli.NewExitError(shutdownErr, 1) return cli.Exit(shutdownErr, 1)
} }
return nil return nil
@ -650,7 +655,7 @@ Main:
func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, error) { func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, error) {
store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration) store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration)
if err != nil { 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) 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) 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 return chain, store, nil
} }

View file

@ -15,7 +15,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View file

@ -56,13 +56,13 @@ func TestCalcHash(t *testing.T) {
cmd := []string{"neo-go", "contract", "calc-hash"} cmd := []string{"neo-go", "contract", "calc-hash"}
t.Run("no sender", func(t *testing.T) { 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) { 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) { 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) { t.Run("invalid nef path", func(t *testing.T) {
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), e.RunWithError(t, append(cmd, "--sender", sender.StringLE(),
@ -289,7 +289,7 @@ func TestContractInitAndCompile(t *testing.T) {
e := testcli.NewExecutor(t, false) e := testcli.NewExecutor(t, false)
t.Run("no path is provided", func(t *testing.T) { 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) { t.Run("invalid path", func(t *testing.T) {
e.RunWithError(t, "neo-go", "contract", "init", "--name", "\x00") 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") manifestPath := filepath.Join(tmpDir, "testcontract.manifest.json")
cmd := []string{"neo-go", "contract", "compile"} cmd := []string{"neo-go", "contract", "compile"}
t.Run("missing source", func(t *testing.T) { 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) 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", e.RunWithError(t, "neo-go", "contract", "deploy",
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
"--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
"--in", "", "--manifest", manifestName) "--in", nefName, "--manifest", manifestName)
}) })
t.Run("missing manifest", func(t *testing.T) { 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], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
"--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr, "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
"--in", nefName, "--manifest", "") "--in", nefName, "--manifest", "")
@ -517,7 +517,7 @@ func TestDeployWithSigners(t *testing.T) {
"[", "str1", "str2", "]") "[", "str1", "str2", "]")
}) })
t.Run("missing RPC", func(t *testing.T) { 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, "--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
"--in", nefName, "--manifest", manifestName, "--in", nefName, "--manifest", manifestName,
"[", "str1", "str2", "]") "[", "str1", "str2", "]")
@ -548,28 +548,29 @@ func TestContractManifestGroups(t *testing.T) {
"--out", nefName, "--manifest", manifestName) "--out", nefName, "--manifest", manifestName)
t.Run("missing wallet", func(t *testing.T) { 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) { t.Run("invalid wallet", func(t *testing.T) {
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", 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) { 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, "--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) { t.Run("invalid NEF file", func(t *testing.T) {
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
"--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, "--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) { t.Run("corrupted NEF file", func(t *testing.T) {
f := filepath.Join(tmpDir, "invalid.nef") f := filepath.Join(tmpDir, "invalid.nef")
require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm)) require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm))
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
"--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount, "--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) { t.Run("invalid manifest file", func(t *testing.T) {
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group", e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
@ -630,9 +631,17 @@ func TestContract_TestInvokeScript(t *testing.T) {
"--out", goodNef, "--manifest", manifestName) "--out", goodNef, "--manifest", manifestName)
t.Run("missing in", func(t *testing.T) { 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]) "--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) { t.Run("unexisting in", func(t *testing.T) {
e.RunWithError(t, "neo-go", "contract", "testinvokescript", e.RunWithError(t, "neo-go", "contract", "testinvokescript",
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--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) { t.Run("check calc hash", func(t *testing.T) {
// missing sender // 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, "--in", nefName,
"--manifest", manifestName) "--manifest", manifestName)
@ -755,7 +764,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
e.RunWithError(t, append(cmd, "--", "notahash")...) e.RunWithError(t, append(cmd, "--", "notahash")...)
}) })
t.Run("missing RPC address", func(t *testing.T) { 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") h.StringLE(), "getValue")
}) })
@ -1038,7 +1047,7 @@ func TestContractInspect(t *testing.T) {
cmd := []string{"neo-go", "contract", "inspect"} cmd := []string{"neo-go", "contract", "inspect"}
t.Run("missing input", func(t *testing.T) { 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) { t.Run("with raw '.go'", func(t *testing.T) {
e.RunWithError(t, append(cmd, "--in", srcPath)...) e.RunWithError(t, append(cmd, "--in", srcPath)...)

View file

@ -9,34 +9,39 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding" "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/smartcontract/rpcbinding"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
var generatorFlags = []cli.Flag{ var generatorFlags = []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "config, c", Name: "config",
Aliases: []string{"c"},
Usage: "Configuration file to use", Usage: "Configuration file to use",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "manifest, m", Name: "manifest",
Aliases: []string{"m"},
Required: true, Required: true,
Usage: "Read contract manifest (*.manifest.json) file", Usage: "Read contract manifest (*.manifest.json) file",
Action: cmdargs.EnsureNotEmpty("manifest"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "out, o", Name: "out",
Aliases: []string{"o"},
Required: true, Required: true,
Usage: "Output of the compiled wrapper", Usage: "Output of the compiled wrapper",
Action: cmdargs.EnsureNotEmpty("out"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "hash", Name: "hash",
Usage: "Smart-contract hash. If not passed, the wrapper will be designed for dynamic hash usage", 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", 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 <file.json> --out <file.go> [--hash <hash>] [--config <config>]", UsageText: "neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]",
Description: `Generates a Go wrapper to use it in other smart contracts. If the 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 --hash flag is provided, CALLT instruction is used for the target contract
@ -48,9 +53,9 @@ var generateWrapperCmd = cli.Command{
Flags: generatorFlags, Flags: generatorFlags,
} }
var generateRPCWrapperCmd = cli.Command{ var generateRPCWrapperCmd = &cli.Command{
Name: "generate-rpcwrapper", 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 <file.json> --out <file.go> [--hash <hash>] [--config <config>]", UsageText: "neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]",
Action: contractGenerateRPCWrapper, Action: contractGenerateRPCWrapper,
Flags: generatorFlags, Flags: generatorFlags,
@ -76,23 +81,23 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error)
if hStr := ctx.String("hash"); len(hStr) != 0 { if hStr := ctx.String("hash"); len(hStr) != 0 {
h, err = util.Uint160DecodeStringLE(strings.TrimPrefix(hStr, "0x")) h, err = util.Uint160DecodeStringLE(strings.TrimPrefix(hStr, "0x"))
if err != nil { 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) m, _, err := readManifest(ctx.String("manifest"), h)
if err != nil { 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() cfg := binding.NewConfig()
if cfgPath := ctx.String("config"); cfgPath != "" { if cfgPath := ctx.String("config"); cfgPath != "" {
bs, err := os.ReadFile(cfgPath) bs, err := os.ReadFile(cfgPath)
if err != nil { 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) err = yaml.Unmarshal(bs, &cfg)
if err != nil { 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 cfg.Manifest = m
@ -100,7 +105,7 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error)
f, err := os.Create(ctx.String("out")) f, err := os.Create(ctx.String("out"))
if err != nil { 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() defer f.Close()
@ -108,7 +113,7 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error)
err = cb(cfg) err = cb(cfg)
if err != nil { 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 return nil
} }

View file

@ -1,4 +1,4 @@
package smartcontract package smartcontract_test
import ( import (
"bytes" "bytes"
@ -6,14 +6,13 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"testing" "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"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli"
) )
func TestGenerate(t *testing.T) { func TestGenerate(t *testing.T) {
@ -124,8 +123,7 @@ func TestGenerate(t *testing.T) {
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01, 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
} }
app := cli.NewApp() e := testcli.NewExecutor(t, false)
app.Commands = []cli.Command{generateWrapperCmd}
rawCfg := `package: wrapper rawCfg := `package: wrapper
hash: ` + h.StringLE() + ` hash: ` + h.StringLE() + `
@ -144,12 +142,12 @@ callflags:
cfgPath := filepath.Join(t.TempDir(), "binding.yml") cfgPath := filepath.Join(t.TempDir(), "binding.yml")
require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) 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, "--manifest", manifestFile,
"--config", cfgPath, "--config", cfgPath,
"--out", outFile, "--out", outFile,
"--hash", h.StringLE(), "--hash", h.StringLE(),
})) }...)
const expected = `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT. const expected = `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
@ -234,11 +232,11 @@ func MyFunc(in map[int]mycontract.Input) []mycontract.Output {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, expected, string(data)) require.Equal(t, expected, string(data))
require.NoError(t, app.Run([]string{"", "generate-wrapper", e.Run(t, []string{"", "contract", "generate-wrapper",
"--manifest", manifestFile, "--manifest", manifestFile,
"--config", cfgPath, "--config", cfgPath,
"--out", outFile, "--out", outFile,
})) }...)
expectedWithDynamicHash := `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT. expectedWithDynamicHash := `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
// Package wrapper contains wrappers for MyContract contract. // 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, 0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04, 0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
} }
app := cli.NewApp() e := testcli.NewExecutor(t, false)
app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd} e.Run(t, []string{"", "contract", "generate-wrapper",
require.NoError(t, app.Run([]string{"", "generate-wrapper",
"--manifest", manifestFile, "--manifest", manifestFile,
"--out", outFile, "--out", outFile,
"--hash", "0x" + h.StringLE(), "--hash", "0x" + h.StringLE(),
})) }...)
data, err := os.ReadFile(outFile) data, err := os.ReadFile(outFile)
require.NoError(t, err) require.NoError(t, err)
@ -378,11 +375,11 @@ func Get() int {
return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int) return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int)
} }
`, string(data)) `, string(data))
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", e.Run(t, []string{"", "contract", "generate-rpcwrapper",
"--manifest", manifestFile, "--manifest", manifestFile,
"--out", outFile, "--out", outFile,
"--hash", "0x" + h.StringLE(), "--hash", "0x" + h.StringLE(),
})) }...)
data, err = os.ReadFile(outFile) data, err = os.ReadFile(outFile)
require.NoError(t, err) require.NoError(t, err)
@ -431,17 +428,16 @@ const rewriteExpectedOutputs = false
func TestGenerateRPCBindings(t *testing.T) { func TestGenerateRPCBindings(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
app := cli.NewApp() e := testcli.NewExecutor(t, false)
app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd}
var checkBinding = func(manifest string, hash string, good string) { var checkBinding = func(manifest string, hash string, good string) {
t.Run(manifest, func(t *testing.T) { t.Run(manifest, func(t *testing.T) {
outFile := filepath.Join(tmpDir, "out.go") outFile := filepath.Join(tmpDir, "out.go")
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", e.Run(t, []string{"", "contract", "generate-rpcwrapper",
"--manifest", manifest, "--manifest", manifest,
"--out", outFile, "--out", outFile,
"--hash", hash, "--hash", hash,
})) }...)
data, err := os.ReadFile(outFile) data, err := os.ReadFile(outFile)
require.NoError(t, err) require.NoError(t, err)
@ -478,8 +474,7 @@ func TestGenerateRPCBindings(t *testing.T) {
func TestAssistedRPCBindings(t *testing.T) { func TestAssistedRPCBindings(t *testing.T) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
app := cli.NewApp() e := testcli.NewExecutor(t, false)
app.Commands = NewCommands()
var checkBinding = func(source string, hasDefinedHash bool, guessEventTypes bool, suffix ...string) { var checkBinding = func(source string, hasDefinedHash bool, guessEventTypes bool, suffix ...string) {
testName := source testName := source
@ -510,7 +505,7 @@ func TestAssistedRPCBindings(t *testing.T) {
if guessEventTypes { if guessEventTypes {
cmd = append(cmd, "--guess-eventtypes") cmd = append(cmd, "--guess-eventtypes")
} }
require.NoError(t, app.Run(cmd)) e.Run(t, cmd...)
cmds := []string{"", "contract", "generate-rpcwrapper", cmds := []string{"", "contract", "generate-rpcwrapper",
"--config", bindingF, "--config", bindingF,
@ -520,7 +515,7 @@ func TestAssistedRPCBindings(t *testing.T) {
if hasDefinedHash { if hasDefinedHash {
cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233") cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233")
} }
require.NoError(t, app.Run(cmds)) e.Run(t, cmds...)
data, err := os.ReadFile(outFile) data, err := os.ReadFile(outFile)
require.NoError(t, err) require.NoError(t, err)
@ -548,28 +543,22 @@ func TestAssistedRPCBindings(t *testing.T) {
} }
func TestGenerate_Errors(t *testing.T) { func TestGenerate_Errors(t *testing.T) {
app := cli.NewApp() e := testcli.NewExecutor(t, false)
app.Commands = []cli.Command{generateWrapperCmd} args := []string{"neo-go", "contract", "generate-wrapper"}
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(append([]string{"", "generate-wrapper"}, args...))
require.True(t, strings.Contains(err.Error(), msg), "got: %v", err)
}
t.Run("invalid hash", func(t *testing.T) { 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) { 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) { 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) { t.Run("empty manifest", func(t *testing.T) {
manifestFile := filepath.Join(t.TempDir(), "invalid.json") manifestFile := filepath.Join(t.TempDir(), "invalid.json")
require.NoError(t, os.WriteFile(manifestFile, []byte("[]"), os.ModePerm)) 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) { t.Run("invalid manifest", func(t *testing.T) {
manifestFile := filepath.Join(t.TempDir(), "invalid.json") manifestFile := filepath.Join(t.TempDir(), "invalid.json")
@ -577,7 +566,7 @@ func TestGenerate_Errors(t *testing.T) {
rawManifest, err := json.Marshal(m) rawManifest, err := json.Marshal(m)
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm)) 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") 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)) require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
t.Run("missing config", func(t *testing.T) { t.Run("missing config", func(t *testing.T) {
checkError(t, "can't read config file", e.RunWithErrorCheckExit(t, "can't read config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
"--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz")...)
"--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz")
}) })
t.Run("invalid config", func(t *testing.T) { t.Run("invalid config", func(t *testing.T) {
rawCfg := `package: wrapper rawCfg := `package: wrapper
@ -605,23 +593,13 @@ callflags:
cfgPath := filepath.Join(t.TempDir(), "binding.yml") cfgPath := filepath.Join(t.TempDir(), "binding.yml")
require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm)) require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
checkError(t, "can't parse config file", e.RunWithErrorCheckExit(t, "can't parse config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
"--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--config", cfgPath, "--out", "zzz")...)
"--config", cfgPath, "--out", "zzz")
}) })
} }
func TestCompile_GuessEventTypes(t *testing.T) { func TestCompile_GuessEventTypes(t *testing.T) {
app := cli.NewApp() e := testcli.NewExecutor(t, false)
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)
}
check := func(t *testing.T, source string, expectedErrText string) { check := func(t *testing.T, source string, expectedErrText string) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
configFile := filepath.Join(source, "invalid.yml") configFile := filepath.Join(source, "invalid.yml")
@ -636,7 +614,7 @@ func TestCompile_GuessEventTypes(t *testing.T) {
"--out", nefF, "--out", nefF,
"--guess-eventtypes", "--guess-eventtypes",
} }
checkError(t, expectedErrText, cmd...) e.RunWithErrorCheckExit(t, expectedErrText, cmd...)
} }
t.Run("not declared in manifest", func(t *testing.T) { 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) { func TestGenerateRPCBindings_Errors(t *testing.T) {
app := cli.NewApp() e := testcli.NewExecutor(t, false)
app.Commands = NewCommands()
app.ExitErrHandler = func(*cli.Context, error) {}
t.Run("duplicating resulting fields", func(t *testing.T) { t.Run("duplicating resulting fields", func(t *testing.T) {
check := func(t *testing.T, packageName string, autogen bool, expectedError string) { check := func(t *testing.T, packageName string, autogen bool, expectedError string) {
tmpDir := t.TempDir() tmpDir := t.TempDir()
@ -687,16 +662,14 @@ func TestGenerateRPCBindings_Errors(t *testing.T) {
if autogen { if autogen {
cmd = append(cmd, "--guess-eventtypes") cmd = append(cmd, "--guess-eventtypes")
} }
require.NoError(t, app.Run(cmd)) e.Run(t, cmd...)
cmds := []string{"", "contract", "generate-rpcwrapper", cmds := []string{"", "contract", "generate-rpcwrapper",
"--config", bindingF, "--config", bindingF,
"--manifest", manifestF, "--manifest", manifestF,
"--out", out, "--out", out,
} }
err := app.Run(cmds) e.RunWithErrorCheckExit(t, expectedError, cmds...)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), expectedError), err.Error())
} }
t.Run("event", func(t *testing.T) { t.Run("event", func(t *testing.T) {

View file

@ -2,7 +2,6 @@ package smartcontract
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
@ -13,34 +12,30 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "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/smartcontract/nef"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func manifestAddGroup(ctx *cli.Context) error { func manifestAddGroup(ctx *cli.Context) error {
if err := cmdargs.EnsureNone(ctx); err != nil { if err := cmdargs.EnsureNone(ctx); err != nil {
return err return err
} }
sender, err := flags.ParseAddress(ctx.String("sender")) sender := ctx.Generic("sender").(*flags.Address)
if err != nil {
return cli.NewExitError(fmt.Errorf("invalid sender: %w", err), 1)
}
nf, _, err := readNEFFile(ctx.String("nef")) nf, _, err := readNEFFile(ctx.String("nef"))
if err != nil { 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") mPath := ctx.String("manifest")
m, _, err := readManifest(mPath, util.Uint160{}) m, _, err := readManifest(mPath, util.Uint160{})
if err != nil { 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) gAcc, w, err := options.GetAccFromContext(ctx)
if err != nil { 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() defer w.Close()
@ -64,21 +59,17 @@ func manifestAddGroup(ctx *cli.Context) error {
rawM, err := json.Marshal(m) rawM, err := json.Marshal(m)
if err != nil { 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) err = os.WriteFile(mPath, rawM, os.ModePerm)
if err != nil { 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 return nil
} }
func readNEFFile(filename string) (*nef.File, []byte, error) { func readNEFFile(filename string) (*nef.File, []byte, error) {
if len(filename) == 0 {
return nil, nil, errors.New("no nef file was provided")
}
f, err := os.ReadFile(filename) f, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, nil, err 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 // it for validness against the provided contract hash. If empty hash is specified
// then no hash-related manifest groups check is performed. // then no hash-related manifest groups check is performed.
func readManifest(filename string, hash util.Uint160) (*manifest.Manifest, []byte, error) { func readManifest(filename string, hash util.Uint160) (*manifest.Manifest, []byte, error) {
if len(filename) == 0 {
return nil, nil, errNoManifestFile
}
manifestBytes, err := os.ReadFile(filename) manifestBytes, err := os.ReadFile(filename)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err

View file

@ -27,25 +27,27 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// addressFlagName is a flag name used for address-related operations. It should be // addressFlagName and addressFlagAlias are a flag name and its alias
// the same within the smartcontract package, thus, use this constant. // used for address-related operations. It should be the same within
const addressFlagName = "address, a" // the smartcontract package, thus, use this constant.
const (
addressFlagName = "address"
addressFlagAlias = "a"
)
var ( 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") 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") 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") 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") errFileExist = errors.New("A file with given smart-contract name already exists")
addressFlag = flags.AddressFlag{ addressFlag = &flags.AddressFlag{
Name: addressFlagName, Name: addressFlagName,
Usage: "address to use as transaction signee (and gas source)", 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. // NewCommands returns 'contract' command.
func NewCommands() []cli.Command { func NewCommands() []*cli.Command {
testInvokeScriptFlags := []cli.Flag{ testInvokeScriptFlags := []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "in, i", Name: "in",
Aliases: []string{"i"},
Required: true,
Usage: "Input location of the .nef file that needs to be invoked", Usage: "Input location of the .nef file that needs to be invoked",
Action: cmdargs.EnsureNotEmpty("in"),
}, },
options.Historic, options.Historic,
} }
@ -96,40 +101,56 @@ func NewCommands() []cli.Command {
invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...) invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...)
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
deployFlags := append(invokeFunctionFlags, []cli.Flag{ deployFlags := append(invokeFunctionFlags, []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "in, i", Name: "in",
Aliases: []string{"i"},
Required: true,
Usage: "Input file for the smart contract (*.nef)", Usage: "Input file for the smart contract (*.nef)",
Action: cmdargs.EnsureNotEmpty("in"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "manifest, m", Name: "manifest",
Aliases: []string{"m"},
Required: true,
Usage: "Manifest input file (*.manifest.json)", Usage: "Manifest input file (*.manifest.json)",
Action: cmdargs.EnsureNotEmpty("manifest"),
}, },
}...) }...)
manifestAddGroupFlags := append([]cli.Flag{ manifestAddGroupFlags := append([]cli.Flag{
cli.StringFlag{ &flags.AddressFlag{
Name: "sender, s", Name: "sender",
Usage: "deploy transaction sender", Aliases: []string{"s"},
Required: true,
Usage: "Deploy transaction sender",
}, },
flags.AddressFlag{ &flags.AddressFlag{
Name: addressFlagName, // use the same name for handler code unification. Name: addressFlagName, // use the same name for handler code unification.
Usage: "account to sign group with", Aliases: []string{addressFlagAlias},
Required: true,
Usage: "Account to sign group with",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "nef, n", Name: "nef",
Usage: "path to the NEF file", Aliases: []string{"n"},
Required: true,
Usage: "Path to the NEF file",
Action: cmdargs.EnsureNotEmpty("nef"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "manifest, m", Name: "manifest",
Usage: "path to the manifest", Aliases: []string{"m"},
Required: true,
Usage: "Path to the manifest",
Action: cmdargs.EnsureNotEmpty("manifest"),
}, },
}, options.Wallet...) }, options.Wallet...)
return []cli.Command{{ return []*cli.Command{{
Name: "contract", Name: "contract",
Usage: "compile - debug - deploy smart contracts", Usage: "Compile - debug - deploy smart contracts",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "compile", 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]", 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 Description: `Compiles given smart contract to a .nef file and emits other associated
information (manifest, bindings configuration, debug information files) if information (manifest, bindings configuration, debug information files) if
@ -141,55 +162,63 @@ func NewCommands() []cli.Command {
`, `,
Action: contractCompile, Action: contractCompile,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "in, i", Name: "in",
Aliases: []string{"i"},
Required: true,
Usage: "Input file for the smart contract to be compiled (*.go file or directory)", Usage: "Input file for the smart contract to be compiled (*.go file or directory)",
Action: cmdargs.EnsureNotEmpty("in"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "out, o", Name: "out",
Aliases: []string{"o"},
Usage: "Output of the compiled contract", Usage: "Output of the compiled contract",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "verbose, v", Name: "verbose",
Aliases: []string{"v"},
Usage: "Print out additional information after a compiling", Usage: "Print out additional information after a compiling",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "debug, d", Name: "debug",
Aliases: []string{"d"},
Usage: "Emit debug info in a separate file", Usage: "Emit debug info in a separate file",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "manifest, m", Name: "manifest",
Aliases: []string{"m"},
Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)", Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "config, c", Name: "config",
Aliases: []string{"c"},
Usage: "Configuration input file (*.yml)", Usage: "Configuration input file (*.yml)",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "no-standards", 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", 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", 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", 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", Name: "bindings",
Usage: "output file for smart-contract bindings configuration", Usage: "Output file for smart-contract bindings configuration",
}, },
}, },
}, },
{ {
Name: "deploy", 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]", 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 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 gas to be added as a network fee to prioritize the transaction. The data
@ -204,7 +233,7 @@ func NewCommands() []cli.Command {
generateRPCWrapperCmd, generateRPCWrapperCmd,
{ {
Name: "invokefunction", 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...]", 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, 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 arguments and signers. Sender is included in the list of signers by default
@ -219,7 +248,7 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "testinvokefunction", 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...]", 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, 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 arguments and signers (sender is not included by default). If no method is given
@ -250,63 +279,78 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "init", 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]", UsageText: "neo-go contract init -n name [--skip-details]",
Action: initSmartContract, Action: initSmartContract,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "name, n", Name: "name",
Usage: "name of the smart-contract to be initialized", Aliases: []string{"n"},
Required: true,
Usage: "Name of the smart-contract to be initialized",
Action: cmdargs.EnsureNotEmpty("name"),
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "skip-details, skip", Name: "skip-details",
Usage: "skip filling in the projects and contract details", Aliases: []string{"skip"},
Usage: "Skip filling in the projects and contract details",
}, },
}, },
}, },
{ {
Name: "inspect", 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]", UsageText: "neo-go contract inspect -i file [-c]",
Action: inspect, Action: inspect,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "compile, c", Name: "compile",
Usage: "compile input file (it should be go code then)", Aliases: []string{"c"},
Usage: "Compile input file (it should be go code then)",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "in, i", Name: "in",
Usage: "input file of the program (either .go or .nef)", Aliases: []string{"i"},
Required: true,
Usage: "Input file of the program (either .go or .nef)",
Action: cmdargs.EnsureNotEmpty("in"),
}, },
}, },
}, },
{ {
Name: "calc-hash", 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", UsageText: "neo-go contract calc-hash -i nef -m manifest -s address",
Action: calcHash, Action: calcHash,
Flags: []cli.Flag{ Flags: []cli.Flag{
flags.AddressFlag{ &flags.AddressFlag{
Name: "sender, s", Name: "sender",
Usage: "sender script hash or address", Aliases: []string{"s"},
Required: true,
Usage: "Sender script hash or address",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "in", Name: "in",
Usage: "path to NEF file", Required: true,
Usage: "Path to NEF file",
Action: cmdargs.EnsureNotEmpty("in"),
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "manifest, m", Name: "manifest",
Usage: "path to manifest file", Aliases: []string{"m"},
Required: true,
Usage: "Path to manifest file",
Action: cmdargs.EnsureNotEmpty("manifest"),
}, },
}, },
}, },
{ {
Name: "manifest", Name: "manifest",
Usage: "manifest-related commands", Usage: "Manifest-related commands",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "add-group", 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", UsageText: "neo-go contract manifest add-group -w wallet [--wallet-config path] -n nef -m manifest -a address -s address",
Action: manifestAddGroup, Action: manifestAddGroup,
Flags: manifestAddGroupFlags, Flags: manifestAddGroupFlags,
@ -323,13 +367,10 @@ func initSmartContract(ctx *cli.Context) error {
return err return err
} }
contractName := ctx.String("name") contractName := ctx.String("name")
if contractName == "" {
return cli.NewExitError(errNoSmartContractName, 1)
}
// Check if the file already exists, if yes, exit // Check if the file already exists, if yes, exit
if _, err := os.Stat(contractName); err == nil { if _, err := os.Stat(contractName); err == nil {
return cli.NewExitError(errFileExist, 1) return cli.Exit(errFileExist, 1)
} }
basePath := contractName basePath := contractName
@ -338,7 +379,7 @@ func initSmartContract(ctx *cli.Context) error {
// create base directory // create base directory
if err := os.Mkdir(basePath, os.ModePerm); err != nil { if err := os.Mkdir(basePath, os.ModePerm); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
m := ProjectConfig{ m := ProjectConfig{
@ -363,10 +404,10 @@ func initSmartContract(ctx *cli.Context) error {
} }
b, err := yaml.Marshal(m) b, err := yaml.Marshal(m)
if err != nil { 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 { 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 ver := ModVersion
@ -382,12 +423,12 @@ require (
github.com/nspcc-dev/neo-go/pkg/interop ` + ver + ` github.com/nspcc-dev/neo-go/pkg/interop ` + ver + `
)`) )`)
if err := os.WriteFile(filepath.Join(basePath, "go.mod"), gm, 0644); err != nil { 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)) data := []byte(fmt.Sprintf(smartContractTmpl, contractName))
if err := os.WriteFile(filepath.Join(basePath, fileName), data, 0644); err != nil { 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) fmt.Fprintf(ctx.App.Writer, "Successfully initialized smart contract [%s]\n", contractName)
@ -400,16 +441,13 @@ func contractCompile(ctx *cli.Context) error {
return err return err
} }
src := ctx.String("in") src := ctx.String("in")
if len(src) == 0 {
return cli.NewExitError(errNoInput, 1)
}
manifestFile := ctx.String("manifest") manifestFile := ctx.String("manifest")
confFile := ctx.String("config") confFile := ctx.String("config")
debugFile := ctx.String("debug") debugFile := ctx.String("debug")
out := ctx.String("out") out := ctx.String("out")
bindings := ctx.String("bindings") bindings := ctx.String("bindings")
if len(confFile) == 0 && (len(manifestFile) != 0 || len(debugFile) != 0 || len(bindings) != 0) { 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 && autocomplete := len(manifestFile) == 0 &&
len(confFile) == 0 && len(confFile) == 0 &&
@ -419,7 +457,7 @@ func contractCompile(ctx *cli.Context) error {
var root string var root string
fileInfo, err := os.Stat(src) fileInfo, err := os.Stat(src)
if err != nil { 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() { if fileInfo.IsDir() {
base := filepath.Base(fileInfo.Name()) base := filepath.Base(fileInfo.Name())
@ -470,7 +508,7 @@ func contractCompile(ctx *cli.Context) error {
result, err := compiler.CompileAndSave(src, o) result, err := compiler.CompileAndSave(src, o)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if ctx.Bool("verbose") { if ctx.Bool("verbose") {
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result)) fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result))
@ -484,34 +522,25 @@ func calcHash(ctx *cli.Context) error {
return err return err
} }
sender := ctx.Generic("sender").(*flags.Address) sender := ctx.Generic("sender").(*flags.Address)
if !sender.IsSet {
return cli.NewExitError("sender is not set", 1)
}
p := ctx.String("in") p := ctx.String("in")
if p == "" {
return cli.NewExitError(errors.New("no .nef file was provided"), 1)
}
mpath := ctx.String("manifest") mpath := ctx.String("manifest")
if mpath == "" {
return cli.NewExitError(errors.New("no manifest file provided"), 1)
}
f, err := os.ReadFile(p) f, err := os.ReadFile(p)
if err != nil { 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) nefFile, err := nef.FileFromBytes(f)
if err != nil { 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) manifestBytes, err := os.ReadFile(mpath)
if err != nil { 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{} m := &manifest.Manifest{}
err = json.Unmarshal(manifestBytes, m) err = json.Unmarshal(manifestBytes, m)
if err != nil { 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()) fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(sender.Uint160(), nefFile.Checksum, m.Name).StringLE())
return nil return nil
@ -528,7 +557,7 @@ func invokeFunction(ctx *cli.Context) error {
func invokeInternal(ctx *cli.Context, signAndPush bool) error { func invokeInternal(ctx *cli.Context, signAndPush bool) error {
var ( var (
err error err error
exitErr *cli.ExitError exitErr cli.ExitCoder
operation string operation string
params []any params []any
paramsStart = 1 paramsStart = 1
@ -539,22 +568,23 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
args := ctx.Args() args := ctx.Args()
if !args.Present() { 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 { 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 { if len(argsSlice) <= 1 {
return cli.NewExitError(errNoMethod, 1) return cli.Exit(errNoMethod, 1)
} }
operation = args[1] operation = argsSlice[1]
paramsStart++ paramsStart++
if len(args) > paramsStart { if len(argsSlice) > paramsStart {
cosignersOffset, scParams, err = cmdargs.ParseParams(args[paramsStart:], true) cosignersOffset, scParams, err = cmdargs.ParseParams(argsSlice[paramsStart:], true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
params = make([]any, len(scParams)) params = make([]any, len(scParams))
for i := range scParams { for i := range scParams {
@ -575,7 +605,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
if signAndPush { if signAndPush {
acc, w, err = options.GetAccFromContext(ctx) acc, w, err = options.GetAccFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer w.Close() defer w.Close()
} }
@ -595,7 +625,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
if signAndPush { if signAndPush {
signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None) signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None)
if err != nil { 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) gctx, cancel := options.GetTimeoutContext(ctx)
@ -615,12 +645,12 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
out := ctx.String("out") out := ctx.String("out")
resp, err = inv.Call(script, operation, params...) resp, err = inv.Call(script, operation, params...)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if resp.State != "HALT" { if resp.State != "HALT" {
errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException) errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException)
if !signAndPush { if !signAndPush {
return cli.NewExitError(errText, 1) return cli.Exit(errText, 1)
} }
action := "send" action := "send"
@ -630,42 +660,38 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
process = "Saving" process = "Saving"
} }
if !ctx.Bool("force") { 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...") fmt.Fprintln(ctx.App.Writer, errText+".\n"+process+" transaction...")
} }
if !signAndPush { if !signAndPush {
b, err := json.MarshalIndent(resp, "", " ") b, err := json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
fmt.Fprintln(ctx.App.Writer, string(b)) fmt.Fprintln(ctx.App.Writer, string(b))
return nil return nil
} }
if len(resp.Script) == 0 { 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) tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed, nil)
if err != 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) return txctx.SignAndSend(ctx, act, acc, tx)
} }
func testInvokeScript(ctx *cli.Context) error { func testInvokeScript(ctx *cli.Context) error {
src := ctx.String("in") src := ctx.String("in")
if len(src) == 0 {
return cli.NewExitError(errNoInput, 1)
}
b, err := os.ReadFile(src) b, err := os.ReadFile(src)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
nefFile, err := nef.FileFromBytes(b) nefFile, err := nef.FileFromBytes(b)
if err != nil { 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) signers, exitErr := cmdargs.GetSignersFromContext(ctx, 0)
@ -683,12 +709,12 @@ func testInvokeScript(ctx *cli.Context) error {
resp, err := inv.Run(nefFile.Script) resp, err := inv.Run(nefFile.Script)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
b, err = json.MarshalIndent(resp, "", " ") b, err = json.MarshalIndent(resp, "", " ")
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
fmt.Fprintln(ctx.App.Writer, string(b)) fmt.Fprintln(ctx.App.Writer, string(b))
@ -714,9 +740,6 @@ func inspect(ctx *cli.Context) error {
} }
in := ctx.String("in") in := ctx.String("in")
compile := ctx.Bool("compile") compile := ctx.Bool("compile")
if len(in) == 0 {
return cli.NewExitError(errNoInput, 1)
}
var ( var (
b []byte b []byte
err error err error
@ -724,16 +747,16 @@ func inspect(ctx *cli.Context) error {
if compile { if compile {
b, err = compiler.Compile(in, nil) b, err = compiler.Compile(in, nil)
if err != 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 { } else {
f, err := os.ReadFile(in) f, err := os.ReadFile(in)
if err != nil { 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) nefFile, err := nef.FileFromBytes(f)
if err != nil { 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 b = nefFile.Script
} }
@ -748,22 +771,22 @@ func inspect(ctx *cli.Context) error {
func contractDeploy(ctx *cli.Context) error { func contractDeploy(ctx *cli.Context) error {
nefFile, f, err := readNEFFile(ctx.String("in")) nefFile, f, err := readNEFFile(ctx.String("in"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
m, manifestBytes, err := readManifest(ctx.String("manifest"), util.Uint160{}) m, manifestBytes, err := readManifest(ctx.String("manifest"), util.Uint160{})
if err != nil { 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} 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 { 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 { 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 { if len(data) != 0 {
appCallParams = append(appCallParams, data[0]) appCallParams = append(appCallParams, data[0])
@ -771,7 +794,7 @@ func contractDeploy(ctx *cli.Context) error {
acc, w, err := options.GetAccFromContext(ctx) acc, w, err := options.GetAccFromContext(ctx)
if err != nil { 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() defer w.Close()
sender := acc.ScriptHash() sender := acc.ScriptHash()
@ -801,12 +824,12 @@ func ParseContractConfig(confFile string) (ProjectConfig, error) {
conf := ProjectConfig{} conf := ProjectConfig{}
confBytes, err := os.ReadFile(confFile) confBytes, err := os.ReadFile(confFile)
if err != nil { if err != nil {
return conf, cli.NewExitError(err, 1) return conf, cli.Exit(err, 1)
} }
err = yaml.Unmarshal(confBytes, &conf) err = yaml.Unmarshal(confBytes, &conf)
if err != nil { 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 return conf, nil
} }

View file

@ -9,7 +9,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )

View file

@ -16,34 +16,36 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ( var (
// GasFlag is a flag used for the additional network fee. // GasFlag is a flag used for the additional network fee.
GasFlag = flags.Fixed8Flag{ GasFlag = &flags.Fixed8Flag{
Name: "gas, g", Name: "gas",
Usage: "network fee to add to the transaction (prioritizing it)", Aliases: []string{"g"},
Usage: "Network fee to add to the transaction (prioritizing it)",
} }
// SysGasFlag is a flag used for the additional system fee. // SysGasFlag is a flag used for the additional system fee.
SysGasFlag = flags.Fixed8Flag{ SysGasFlag = &flags.Fixed8Flag{
Name: "sysgas, e", Name: "sysgas",
Usage: "system fee to add to the transaction (compensating for execution)", Aliases: []string{"e"},
Usage: "System fee to add to the transaction (compensating for execution)",
} }
// OutFlag is a flag used for file output. // OutFlag is a flag used for file output.
OutFlag = cli.StringFlag{ OutFlag = &cli.StringFlag{
Name: "out", 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 is a flag used to force transaction send.
ForceFlag = cli.BoolFlag{ ForceFlag = &cli.BoolFlag{
Name: "force", Name: "force",
Usage: "Do not ask for a confirmation (and ignore errors)", 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 is a flag used to wait for the transaction to be included in a block.
AwaitFlag = cli.BoolFlag{ AwaitFlag = &cli.BoolFlag{
Name: "await", 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() promptTime := time.Now()
err := input.ConfirmTx(ctx.App.Writer, tx) err := input.ConfirmTx(ctx.App.Writer, tx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
waitTime := time.Since(promptTime) waitTime := time.Since(promptTime)
// Compensate for confirmation waiting. // 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) resTx, vub, err = act.SignAndSend(tx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if ctx.Bool("await") { if ctx.Bool("await") {
aer, err = act.Wait(resTx, vub, err) aer, err = act.Wait(resTx, vub, err)
if err != nil { 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 { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer) DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer)

View file

@ -16,20 +16,20 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func cancelTx(ctx *cli.Context) error { func cancelTx(ctx *cli.Context) error {
args := ctx.Args() args := ctx.Args().Slice()
if len(args) == 0 { 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 { } 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")) txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
if err != nil { 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) gctx, cancel := options.GetTimeoutContext(ctx)
@ -37,13 +37,13 @@ func cancelTx(ctx *cli.Context) error {
acc, w, err := options.GetAccFromContext(ctx) acc, w, err := options.GetAccFromContext(ctx)
if err != nil { 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() defer w.Close()
signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry) signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry)
if err != nil { 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) c, a, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
if exitErr != nil { if exitErr != nil {
@ -52,11 +52,11 @@ func cancelTx(ctx *cli.Context) error {
mainTx, _ := c.GetRawTransactionVerbose(txHash) mainTx, _ := c.GetRawTransactionVerbose(txHash)
if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) { 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()) { 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 { 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 return nil
}) })
if err != 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 var res *state.AppExecResult
if ctx.Bool("await") { if ctx.Bool("await") {
@ -82,19 +82,19 @@ func cancelTx(ctx *cli.Context) error {
if err != nil { if err != nil {
if errors.Is(err, waiter.ErrTxNotAccepted) { if errors.Is(err, waiter.ErrTxNotAccepted) {
if mainTx == nil { 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) 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 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) { if txHash.Equals(res.Container) {
tx, err := c.GetRawTransactionVerbose(txHash) tx, err := c.GetRawTransactionVerbose(txHash)
if err != nil { 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") fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted")
} }

View file

@ -11,27 +11,32 @@ import (
"github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/cli/txctx"
vmcli "github.com/nspcc-dev/neo-go/cli/vm" vmcli "github.com/nspcc-dev/neo-go/cli/vm"
"github.com/nspcc-dev/neo-go/pkg/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. // NewCommands returns util commands for neo-go CLI.
func NewCommands() []cli.Command { func NewCommands() []*cli.Command {
txDumpFlags := append([]cli.Flag{}, options.RPC...) // By default, RPC flag is required. sendtx and txdump may be called without provided rpc-endpoint.
rpcFlagOriginal, _ := options.RPC[0].(*cli.StringFlag)
rpcFlag := *rpcFlagOriginal
rpcFlag.Required = false
txDumpFlags := append([]cli.Flag{&rpcFlag}, options.RPC[1:]...)
txSendFlags := append(txDumpFlags, txctx.AwaitFlag) txSendFlags := append(txDumpFlags, txctx.AwaitFlag)
txCancelFlags := append([]cli.Flag{ txCancelFlags := append([]cli.Flag{
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Usage: "address to use as conflicting transaction signee (and gas source)", Aliases: []string{"a"},
Usage: "Address to use as conflicting transaction signee (and gas source)",
}, },
txctx.GasFlag, txctx.GasFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
}, options.RPC...) }, options.RPC...)
txCancelFlags = append(txCancelFlags, options.Wallet...) txCancelFlags = append(txCancelFlags, options.Wallet...)
return []cli.Command{ return []*cli.Command{
{ {
Name: "util", Name: "util",
Usage: "Various helper commands", Usage: "Various helper commands",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "convert", Name: "convert",
Usage: "Convert provided argument into other possible formats", Usage: "Convert provided argument into other possible formats",
@ -44,7 +49,7 @@ func NewCommands() []cli.Command {
{ {
Name: "sendtx", Name: "sendtx",
Usage: "Send complete transaction stored in a context file", Usage: "Send complete transaction stored in a context file",
UsageText: "sendtx [-r <endpoint>] <file.in> [--await]", UsageText: "sendtx [-r <endpoint>] [--await] <file.in>",
Description: `Sends the transaction from the given context file to the given RPC node if it's 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 completely signed and ready. This command expects a ContractParametersContext
JSON file for input, it can't handle binary (or hex- or base64-encoded) JSON file for input, it can't handle binary (or hex- or base64-encoded)
@ -57,7 +62,7 @@ func NewCommands() []cli.Command {
{ {
Name: "canceltx", Name: "canceltx",
Usage: "Cancel transaction by sending conflicting transaction", Usage: "Cancel transaction by sending conflicting transaction",
UsageText: "canceltx <txid> -r <endpoint> --wallet <wallet> [--account <account>] [--wallet-config <path>] [--gas <gas>] [--await]", UsageText: "canceltx -r <endpoint> --wallet <wallet> [--address <account>] [--wallet-config <path>] [--gas <gas>] [--await] <txid>",
Description: `Aims to prevent a transaction from being added to the blockchain by dispatching a more 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 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 be the transaction hash. If another account is not specified, the conflicting transaction is
@ -90,16 +95,17 @@ func NewCommands() []cli.Command {
{ {
Name: "ops", 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.", 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 <base64/hex-encoded script> [-i path-to-file] [--hex]", UsageText: "ops [-i path-to-file] [--hex] <base64/hex-encoded script>",
Action: handleOps, Action: handleOps,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ &cli.StringFlag{
Name: "in, i", Name: "in",
Usage: "input file containing base64- or hex- encoded script representation", Aliases: []string{"i"},
Usage: "Input file containing base64- or hex- encoded script representation",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: "hex", 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 { func handleParse(ctx *cli.Context) error {
res, err := vmcli.Parse(ctx.Args()) res, err := vmcli.Parse(ctx.Args().Slice())
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
fmt.Fprint(ctx.App.Writer, res) fmt.Fprint(ctx.App.Writer, res)
return nil return nil
@ -127,21 +133,21 @@ func handleOps(ctx *cli.Context) error {
if len(in) != 0 { if len(in) != 0 {
b, err := os.ReadFile(in) b, err := os.ReadFile(in)
if err != nil { 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) s = string(b)
} else { } else {
if !ctx.Args().Present() { 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) b, err = base64.StdEncoding.DecodeString(s)
if err != nil || ctx.Bool("hex") { if err != nil || ctx.Bool("hex") {
b, err = hex.DecodeString(s) b, err = hex.DecodeString(s)
} }
if err != nil { 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 := vm.New()
v.LoadScript(b) v.LoadScript(b)

View file

@ -8,30 +8,30 @@ import (
"github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/cli/paramcontext"
"github.com/nspcc-dev/neo-go/cli/query" "github.com/nspcc-dev/neo-go/cli/query"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func txDump(ctx *cli.Context) error { func txDump(ctx *cli.Context) error {
args := ctx.Args() args := ctx.Args().Slice()
if len(args) == 0 { if len(args) == 0 {
return cli.NewExitError("missing input file", 1) return cli.Exit("missing input file", 1)
} else if len(args) > 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]) c, err := paramcontext.Read(args[0])
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
tx, ok := c.Verifiable.(*transaction.Transaction) tx, ok := c.Verifiable.(*transaction.Transaction)
if !ok { 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) err = query.DumpApplicationLog(ctx, nil, tx, nil, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if ctx.String(options.RPCEndpointFlag) != "" { if ctx.String(options.RPCEndpointFlag) != "" {
@ -41,15 +41,15 @@ func txDump(ctx *cli.Context) error {
var err error var err error
cl, err := options.GetRPCClient(gctx, ctx) cl, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
res, err := cl.InvokeScript(tx.Script, tx.Signers) res, err := cl.InvokeScript(tx.Script, tx.Signers)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
resS, err := json.MarshalIndent(res, "", " ") resS, err := json.MarshalIndent(res, "", " ")
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
fmt.Fprintln(ctx.App.Writer, string(resS)) fmt.Fprintln(ctx.App.Writer, string(resS))
} }

View file

@ -8,25 +8,25 @@ import (
"github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/cli/txctx"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func sendTx(ctx *cli.Context) error { func sendTx(ctx *cli.Context) error {
args := ctx.Args() args := ctx.Args().Slice()
if len(args) == 0 { if len(args) == 0 {
return cli.NewExitError("missing input file", 1) return cli.Exit("missing input file", 1)
} else if len(args) > 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]) pc, err := paramcontext.Read(args[0])
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
tx, err := pc.GetCompleteTransaction() tx, err := pc.GetCompleteTransaction()
if err != nil { 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) gctx, cancel := options.GetTimeoutContext(ctx)
@ -34,18 +34,18 @@ func sendTx(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { 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) res, err := c.SendRawTransaction(tx)
if err != nil { 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 var aer *state.AppExecResult
if ctx.Bool("await") { if ctx.Bool("await") {
version, err := c.GetVersion() version, err := c.GetVersion()
aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err) aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err)
if err != nil { 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) txctx.DumpTransactionInfo(ctx.App.Writer, res, aer)

View file

@ -44,7 +44,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util/slice" "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"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "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"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
@ -70,22 +70,22 @@ const (
) )
var ( var (
historicFlag = cli.IntFlag{ historicFlag = &cli.IntFlag{
Name: historicFlagFullName, Name: historicFlagFullName,
Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " + 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.", "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, Name: gasFlagFullName,
Usage: "GAS limit for this execution (integer number, satoshi).", Usage: "GAS limit for this execution (integer number, satoshi).",
} }
hashFlag = cli.StringFlag{ hashFlag = &flags.AddressFlag{
Name: hashFlagFullName, Name: hashFlagFullName,
Usage: "Smart-contract hash in LE form or address", Usage: "Smart-contract hash in LE form or address",
} }
) )
var commands = []cli.Command{ var commands = []*cli.Command{
{ {
Name: "exit", Name: "exit",
Usage: "Exit the VM prompt", Usage: "Exit the VM prompt",
@ -339,8 +339,9 @@ Example:
Usage: "Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration)", Usage: "Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration)",
UsageText: `env [-v]`, UsageText: `env [-v]`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: verboseFlagFullName + ",v", Name: verboseFlagFullName,
Aliases: []string{"v"},
Usage: "Print the whole blockchain node configuration.", Usage: "Print the whole blockchain node configuration.",
}, },
}, },
@ -353,14 +354,16 @@ Example:
{ {
Name: "storage", Name: "storage",
Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation", Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation",
UsageText: `storage <hash-or-address-or-id> [<prefix>] [--backwards] [--diff]`, UsageText: `storage [--backwards] [--diff] <hash-or-address-or-id> [<prefix>]`,
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: backwardsFlagFullName + ",b", Name: backwardsFlagFullName,
Aliases: []string{"b"},
Usage: "Backwards traversal direction", Usage: "Backwards traversal direction",
}, },
cli.BoolFlag{ &cli.BoolFlag{
Name: diffFlagFullName + ",d", 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.", 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.",
}, },
}, },
@ -400,7 +403,7 @@ func init() {
if !c.Hidden { if !c.Hidden {
var flagsItems []readline.PrefixCompleterInterface var flagsItems []readline.PrefixCompleterInterface
for _, f := range c.Flags { 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])) flagsItems = append(flagsItems, readline.PcItem("--"+names[0]))
} }
pcItems = append(pcItems, readline.PcItem(c.Name, flagsItems...)) 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) log, _, logCloser, err := options.HandleLoggingParams(false, cfg.ApplicationConfiguration)
if err != nil { 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 { filter := zap.WrapCore(func(z zapcore.Core) zapcore.Core {
return options.NewFilteringCore(z, func(entry zapcore.Entry) bool { 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) chain, err := core.NewBlockchain(store, cfg.Blockchain(), fLog)
if err != nil { 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. // Do not run chain, we need only state-related functionality from it.
ic, err := chain.GetTestVM(trigger.Application, nil, nil) ic, err := chain.GetTestVM(trigger.Application, nil, nil)
if err != 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{ vmcli := CLI{
@ -610,7 +613,7 @@ func handleJump(c *cli.Context) error {
} }
func getInstructionParameter(c *cli.Context) (int, error) { func getInstructionParameter(c *cli.Context) (int, error) {
args := c.Args() args := c.Args().Slice()
if len(args) != 1 { if len(args) != 1 {
return 0, fmt.Errorf("%w: <ip>", ErrMissingParameter) return 0, fmt.Errorf("%w: <ip>", ErrMissingParameter)
} }
@ -682,15 +685,12 @@ func getHashFlag(c *cli.Context) (util.Uint160, error) {
if !c.IsSet(hashFlagFullName) { if !c.IsSet(hashFlagFullName) {
return util.Uint160{}, nil return util.Uint160{}, nil
} }
h, err := flags.ParseAddress(c.String(hashFlagFullName)) h := c.Generic(hashFlagFullName).(*flags.Address)
if err != nil { return h.Uint160(), nil
return util.Uint160{}, fmt.Errorf("failed to parse contract hash: %w", err)
}
return h, nil
} }
func handleLoadNEF(c *cli.Context) error { func handleLoadNEF(c *cli.Context) error {
args := c.Args() args := c.Args().Slice()
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("%w: <nef> is required", ErrMissingParameter) return fmt.Errorf("%w: <nef> is required", ErrMissingParameter)
} }
@ -734,7 +734,7 @@ func handleLoadNEF(c *cli.Context) error {
} }
var signers []transaction.Signer var signers []transaction.Signer
if signersStartOffset != 0 && len(args) > signersStartOffset { if signersStartOffset != 0 && len(args) > signersStartOffset {
signers, err = cmdargs.ParseSigners(c.Args()[signersStartOffset:]) signers, err = cmdargs.ParseSigners(args[signersStartOffset:])
if err != nil { if err != nil {
return fmt.Errorf("%w: failed to parse signers: %w", ErrInvalidParameter, err) 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 { func handleLoadBase64(c *cli.Context) error {
args := c.Args() args := c.Args().Slice()
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("%w: <string>", ErrMissingParameter) return fmt.Errorf("%w: <string>", ErrMissingParameter)
} }
@ -801,7 +801,7 @@ func createFakeTransaction(script []byte, signers []transaction.Signer) *transac
} }
func handleLoadHex(c *cli.Context) error { func handleLoadHex(c *cli.Context) error {
args := c.Args() args := c.Args().Slice()
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("%w: <string>", ErrMissingParameter) return fmt.Errorf("%w: <string>", ErrMissingParameter)
} }
@ -833,7 +833,7 @@ func handleLoadHex(c *cli.Context) error {
} }
func handleLoadGo(c *cli.Context) error { func handleLoadGo(c *cli.Context) error {
args := c.Args() args := c.Args().Slice()
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("%w: <file>", ErrMissingParameter) return fmt.Errorf("%w: <file>", ErrMissingParameter)
} }
@ -885,7 +885,7 @@ func handleLoadGo(c *cli.Context) error {
} }
func handleLoadTx(c *cli.Context) error { func handleLoadTx(c *cli.Context) error {
args := c.Args() args := c.Args().Slice()
if len(args) < 1 { if len(args) < 1 {
return fmt.Errorf("%w: <file-or-hash>", ErrMissingParameter) return fmt.Errorf("%w: <file-or-hash>", ErrMissingParameter)
} }
@ -933,7 +933,7 @@ func handleLoadDeployed(c *cli.Context) error {
if !c.Args().Present() { if !c.Args().Present() {
return errors.New("contract hash, address or ID is mandatory argument") return errors.New("contract hash, address or ID is mandatory argument")
} }
args := c.Args() args := c.Args().Slice()
hashOrID := args[0] hashOrID := args[0]
ic := getInteropContextFromContext(c.App) ic := getInteropContextFromContext(c.App)
h, err := flags.ParseAddress(hashOrID) h, err := flags.ParseAddress(hashOrID)
@ -1062,7 +1062,7 @@ func getManifestFromFile(name string) (*manifest.Manifest, error) {
func handleRun(c *cli.Context) error { func handleRun(c *cli.Context) error {
v := getVMFromContext(c.App) v := getVMFromContext(c.App)
cs := getContractStateFromContext(c.App) cs := getContractStateFromContext(c.App)
args := c.Args() args := c.Args().Slice()
if len(args) != 0 { if len(args) != 0 {
var ( var (
params []stackitem.Item params []stackitem.Item
@ -1181,7 +1181,7 @@ func handleStep(c *cli.Context) error {
return nil return nil
} }
v := getVMFromContext(c.App) v := getVMFromContext(c.App)
args := c.Args() args := c.Args().Slice()
if len(args) > 0 { if len(args) > 0 {
n, err = strconv.Atoi(args[0]) n, err = strconv.Atoi(args[0])
if err != nil { if err != nil {
@ -1422,7 +1422,7 @@ func (c *CLI) Run() error {
} }
func handleParse(c *cli.Context) error { func handleParse(c *cli.Context) error {
res, err := Parse(c.Args()) res, err := Parse(c.Args().Slice())
if err != nil { if err != nil {
return err return err
} }

View file

@ -1188,7 +1188,7 @@ func TestDumpStorage(t *testing.T) {
"storage "+address.Uint160ToString(h), "storage "+address.Uint160ToString(h),
"storage 1", "storage 1",
"storage 1 "+hex.EncodeToString(expected[0].Key), "storage 1 "+hex.EncodeToString(expected[0].Key),
"storage 1 --backwards", "storage --backwards 1",
"exit", "exit",
) )
e.checkStorage(t, expected...) e.checkStorage(t, expected...)
@ -1214,11 +1214,11 @@ func TestDumpStorageDiff(t *testing.T) {
diff := storage.KeyValue{Key: []byte{3}, Value: []byte{3}} diff := storage.KeyValue{Key: []byte{3}, Value: []byte{3}}
e.runProg(t, e.runProg(t,
"storage 1", "storage 1",
"storage 1 --diff", "storage --diff 1",
"loadhex "+hex.EncodeToString(script.Bytes()), "loadhex "+hex.EncodeToString(script.Bytes()),
"run", "run",
"storage 1", "storage 1",
"storage 1 --diff", "storage --diff 1",
"exit", "exit",
) )

View file

@ -8,16 +8,16 @@ import (
"github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/cmdargs"
"github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/options"
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" "github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// NewCommands returns 'vm' command. // NewCommands returns 'vm' command.
func NewCommands() []cli.Command { func NewCommands() []*cli.Command {
cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath} cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath}
cfgFlags = append(cfgFlags, options.Network...) cfgFlags = append(cfgFlags, options.Network...)
return []cli.Command{{ return []*cli.Command{{
Name: "vm", Name: "vm",
Usage: "start the virtual machine", Usage: "Start the virtual machine",
Action: startVMPrompt, Action: startVMPrompt,
Flags: cfgFlags, Flags: cfgFlags,
}} }}
@ -30,7 +30,7 @@ func startVMPrompt(ctx *cli.Context) error {
cfg, err := options.GetConfigFromContext(ctx) cfg, err := options.GetConfigFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if ctx.NumFlags() == 0 { if ctx.NumFlags() == 0 {
cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.InMemoryDB 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) p, err := NewWithConfig(true, os.Exit, &readline.Config{}, cfg)
if err != nil { 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() return p.Run()
} }

View file

@ -39,7 +39,7 @@ func TestRegisterCandidate(t *testing.T) {
e.CheckEOF(t) e.CheckEOF(t)
// missing address // 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], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
"--wallet", testcli.ValidatorWallet) "--wallet", testcli.ValidatorWallet)
@ -131,7 +131,7 @@ func TestRegisterCandidate(t *testing.T) {
}) })
// missing address // 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], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
"--wallet", testcli.ValidatorWallet) "--wallet", testcli.ValidatorWallet)
// additional argument // additional argument
@ -153,7 +153,7 @@ func TestRegisterCandidate(t *testing.T) {
require.Equal(t, 0, len(vs)) require.Equal(t, 0, len(vs))
// query voter: missing address // 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. // Excessive parameters.
e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress, validatorAddress) 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") e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something")

View file

@ -12,7 +12,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/state" "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/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter" "github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func signStoredTransaction(ctx *cli.Context) error { func signStoredTransaction(ctx *cli.Context) error {
@ -28,52 +28,52 @@ func signStoredTransaction(ctx *cli.Context) error {
pc, err := paramcontext.Read(ctx.String("in")) pc, err := paramcontext.Read(ctx.String("in"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if !addrFlag.IsSet { if !addrFlag.IsSet {
return cli.NewExitError("address was not provided", 1) return cli.Exit("address was not provided", 1)
} }
acc, _, err := options.GetAccFromContext(ctx) acc, _, err := options.GetAccFromContext(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
tx, ok := pc.Verifiable.(*transaction.Transaction) tx, ok := pc.Verifiable.(*transaction.Transaction)
if !ok { 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()) { 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() { if acc.CanSign() {
sign := acc.SignHashable(pc.Network, pc.Verifiable) sign := acc.SignHashable(pc.Network, pc.Verifiable)
if err := pc.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil { 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 == "" { } 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. // Not saving and not sending, print.
if out == "" && rpcNode == "" { if out == "" && rpcNode == "" {
txt, err := json.MarshalIndent(pc, " ", " ") txt, err := json.MarshalIndent(pc, " ", " ")
if err != nil { 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)) fmt.Fprintln(ctx.App.Writer, string(txt))
return nil return nil
} }
if out != "" { if out != "" {
if err := paramcontext.Save(pc, out); err != nil { 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 != "" { if rpcNode != "" {
tx, err = pc.GetCompleteTransaction() tx, err = pc.GetCompleteTransaction()
if err != nil { 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) gctx, cancel := options.GetTimeoutContext(ctx)
@ -82,17 +82,17 @@ func signStoredTransaction(ctx *cli.Context) error {
var err error // `GetRPCClient` returns specialized type. var err error // `GetRPCClient` returns specialized type.
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { 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) res, err := c.SendRawTransaction(tx)
if err != nil { 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") { if ctx.Bool("await") {
version, err := c.GetVersion() version, err := c.GetVersion()
aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err) aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err)
if err != nil { 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)
} }
} }
} }

View file

@ -79,10 +79,10 @@ func TestSignMultisigTx(t *testing.T) {
"--out", txPath) "--out", txPath)
// missing wallet // missing wallet
e.RunWithError(t, "neo-go", "wallet", "sign") e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "wallet", "sign")
// missing in // missing in
e.RunWithError(t, "neo-go", "wallet", "sign", e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "wallet", "sign",
"--wallet", wallet2Path) "--wallet", wallet2Path)
// missing address // missing address

View file

@ -19,20 +19,22 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/nspcc-dev/neo-go/pkg/wallet" "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) maxIters := strconv.Itoa(config.DefaultMaxIteratorResultItems)
tokenAddressFlag := flags.AddressFlag{ tokenAddressFlag := &flags.AddressFlag{
Name: "token", Name: "token",
Usage: "Token contract address or hash in LE", Usage: "Token contract address or hash in LE",
Required: true,
} }
ownerAddressFlag := flags.AddressFlag{ ownerAddressFlag := &flags.AddressFlag{
Name: "address", Name: "address",
Usage: "NFT owner address or hash in LE", Usage: "NFT owner address or hash in LE",
Required: true,
} }
tokenID := cli.StringFlag{ tokenID := &cli.StringFlag{
Name: "id", Name: "id",
Usage: "Hex-encoded token ID", Usage: "Hex-encoded token ID",
} }
@ -45,10 +47,10 @@ func newNEP11Commands() []cli.Command {
copy(transferFlags, baseTransferFlags) copy(transferFlags, baseTransferFlags)
transferFlags = append(transferFlags, tokenID) transferFlags = append(transferFlags, tokenID)
transferFlags = append(transferFlags, options.RPC...) transferFlags = append(transferFlags, options.RPC...)
return []cli.Command{ return []*cli.Command{
{ {
Name: "balance", Name: "balance",
Usage: "get address balance", Usage: "Get address balance",
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>] [--id <token-id>]", UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>] [--id <token-id>]",
Description: `Prints NEP-11 balances for address and assets/IDs specified. By default (no Description: `Prints NEP-11 balances for address and assets/IDs specified. By default (no
address or token parameter) all tokens (NFT contracts) for all accounts in address or token parameter) all tokens (NFT contracts) for all accounts in
@ -70,14 +72,14 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "import", Name: "import",
Usage: "import NEP-11 token to a wallet", Usage: "Import NEP-11 token to a wallet",
UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --token <hash>", UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] --token <hash>",
Action: importNEP11Token, Action: importNEP11Token,
Flags: importFlags, Flags: importFlags,
}, },
{ {
Name: "info", Name: "info",
Usage: "print imported NEP-11 token info", Usage: "Print imported NEP-11 token info",
UsageText: "print -w wallet [--wallet-config path] [--token <hash-or-name>]", UsageText: "print -w wallet [--wallet-config path] [--token <hash-or-name>]",
Action: printNEP11Info, Action: printNEP11Info,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -88,7 +90,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "remove", Name: "remove",
Usage: "remove NEP-11 token from the wallet", Usage: "Remove NEP-11 token from the wallet",
UsageText: "remove -w wallet [--wallet-config path] --token <hash-or-name>", UsageText: "remove -w wallet [--wallet-config path] --token <hash-or-name>",
Action: removeNEP11Token, Action: removeNEP11Token,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -100,8 +102,8 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "transfer", Name: "transfer",
Usage: "transfer NEP-11 tokens", Usage: "Transfer NEP-11 tokens",
UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [--await] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]", UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [--await] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
Action: transferNEP11, Action: transferNEP11,
Flags: transferFlags, Flags: transferFlags,
Description: `Transfers specified NEP-11 token with optional cosigners list attached to Description: `Transfers specified NEP-11 token with optional cosigners list attached to
@ -116,7 +118,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "properties", Name: "properties",
Usage: "print properties of NEP-11 token", Usage: "Print properties of NEP-11 token",
UsageText: "properties --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]", UsageText: "properties --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]",
Action: printNEP11Properties, Action: printNEP11Properties,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -127,7 +129,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "ownerOf", Name: "ownerOf",
Usage: "print owner of non-divisible NEP-11 token with the specified ID", Usage: "Print owner of non-divisible NEP-11 token with the specified ID",
UsageText: "ownerOf --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]", UsageText: "ownerOf --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]",
Action: printNEP11NDOwner, Action: printNEP11NDOwner,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -138,7 +140,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "ownerOfD", Name: "ownerOfD",
Usage: "print set of owners of divisible NEP-11 token with the specified ID (" + maxIters + " will be printed at max)", Usage: "Print set of owners of divisible NEP-11 token with the specified ID (" + maxIters + " will be printed at max)",
UsageText: "ownerOfD --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]", UsageText: "ownerOfD --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]",
Action: printNEP11DOwner, Action: printNEP11DOwner,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -149,7 +151,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "tokensOf", Name: "tokensOf",
Usage: "print list of tokens IDs for the specified NFT owner (" + maxIters + " will be printed at max)", Usage: "Print list of tokens IDs for the specified NFT owner (" + maxIters + " will be printed at max)",
UsageText: "tokensOf --rpc-endpoint <node> [--timeout <time>] --token <hash> --address <addr> [--historic <block/hash>]", UsageText: "tokensOf --rpc-endpoint <node> [--timeout <time>] --token <hash> --address <addr> [--historic <block/hash>]",
Action: printNEP11TokensOf, Action: printNEP11TokensOf,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -160,7 +162,7 @@ func newNEP11Commands() []cli.Command {
}, },
{ {
Name: "tokens", Name: "tokens",
Usage: "print list of tokens IDs minted by the specified NFT (optional method; " + maxIters + " will be printed at max)", Usage: "Print list of tokens IDs minted by the specified NFT (optional method; " + maxIters + " will be printed at max)",
UsageText: "tokens --rpc-endpoint <node> [--timeout <time>] --token <hash> [--historic <block/hash>]", UsageText: "tokens --rpc-endpoint <node> [--timeout <time>] --token <hash> [--historic <block/hash>]",
Action: printNEP11Tokens, Action: printNEP11Tokens,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
@ -246,17 +248,13 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
return err return err
} }
tokenHash := ctx.Generic("token").(*flags.Address) tokenHash := ctx.Generic("token").(*flags.Address)
if !tokenHash.IsSet {
return cli.NewExitError("token contract hash was not set", 1)
}
tokenID := ctx.String("id") tokenID := ctx.String("id")
if tokenID == "" { if tokenID == "" {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.Exit(errors.New("token ID should be specified"), 1)
} }
tokenIDBytes, err := hex.DecodeString(tokenID) tokenIDBytes, err := hex.DecodeString(tokenID)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1) return cli.Exit(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -271,7 +269,7 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
n11 := nep11.NewDivisibleReader(inv, tokenHash.Uint160()) n11 := nep11.NewDivisibleReader(inv, tokenHash.Uint160())
result, err := n11.OwnerOfExpanded(tokenIDBytes, config.DefaultMaxIteratorResultItems) result, err := n11.OwnerOfExpanded(tokenIDBytes, config.DefaultMaxIteratorResultItems)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 divisible `ownerOf` method: %s", err.Error()), 1) return cli.Exit(fmt.Sprintf("failed to call NEP-11 divisible `ownerOf` method: %s", err.Error()), 1)
} }
for _, h := range result { for _, h := range result {
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(h)) fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(h))
@ -280,7 +278,7 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
n11 := nep11.NewNonDivisibleReader(inv, tokenHash.Uint160()) n11 := nep11.NewNonDivisibleReader(inv, tokenHash.Uint160())
result, err := n11.OwnerOf(tokenIDBytes) result, err := n11.OwnerOf(tokenIDBytes)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 non-divisible `ownerOf` method: %s", err.Error()), 1) return cli.Exit(fmt.Sprintf("failed to call NEP-11 non-divisible `ownerOf` method: %s", err.Error()), 1)
} }
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result)) fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result))
} }
@ -291,15 +289,7 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
func printNEP11TokensOf(ctx *cli.Context) error { func printNEP11TokensOf(ctx *cli.Context) error {
var err error var err error
tokenHash := ctx.Generic("token").(*flags.Address) tokenHash := ctx.Generic("token").(*flags.Address)
if !tokenHash.IsSet {
return cli.NewExitError("token contract hash was not set", 1)
}
acc := ctx.Generic("address").(*flags.Address) acc := ctx.Generic("address").(*flags.Address)
if !acc.IsSet {
return cli.NewExitError("owner address flag was not set", 1)
}
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
@ -311,7 +301,7 @@ func printNEP11TokensOf(ctx *cli.Context) error {
n11 := nep11.NewBaseReader(inv, tokenHash.Uint160()) n11 := nep11.NewBaseReader(inv, tokenHash.Uint160())
result, err := n11.TokensOfExpanded(acc.Uint160(), config.DefaultMaxIteratorResultItems) result, err := n11.TokensOfExpanded(acc.Uint160(), config.DefaultMaxIteratorResultItems)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `tokensOf` method: %s", err.Error()), 1) return cli.Exit(fmt.Sprintf("failed to call NEP-11 `tokensOf` method: %s", err.Error()), 1)
} }
for i := range result { for i := range result {
@ -326,10 +316,6 @@ func printNEP11Tokens(ctx *cli.Context) error {
return err return err
} }
tokenHash := ctx.Generic("token").(*flags.Address) tokenHash := ctx.Generic("token").(*flags.Address)
if !tokenHash.IsSet {
return cli.NewExitError("token contract hash was not set", 1)
}
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
@ -341,7 +327,7 @@ func printNEP11Tokens(ctx *cli.Context) error {
n11 := nep11.NewBaseReader(inv, tokenHash.Uint160()) n11 := nep11.NewBaseReader(inv, tokenHash.Uint160())
result, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems) result, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call optional NEP-11 `tokens` method: %s", err.Error()), 1) return cli.Exit(fmt.Sprintf("failed to call optional NEP-11 `tokens` method: %s", err.Error()), 1)
} }
for i := range result { for i := range result {
@ -356,17 +342,13 @@ func printNEP11Properties(ctx *cli.Context) error {
return err return err
} }
tokenHash := ctx.Generic("token").(*flags.Address) tokenHash := ctx.Generic("token").(*flags.Address)
if !tokenHash.IsSet {
return cli.NewExitError("token contract hash was not set", 1)
}
tokenID := ctx.String("id") tokenID := ctx.String("id")
if tokenID == "" { if tokenID == "" {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.Exit(errors.New("token ID should be specified"), 1)
} }
tokenIDBytes, err := hex.DecodeString(tokenID) tokenIDBytes, err := hex.DecodeString(tokenID)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1) return cli.Exit(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -380,12 +362,12 @@ func printNEP11Properties(ctx *cli.Context) error {
n11 := nep11.NewBaseReader(inv, tokenHash.Uint160()) n11 := nep11.NewBaseReader(inv, tokenHash.Uint160())
result, err := n11.Properties(tokenIDBytes) result, err := n11.Properties(tokenIDBytes)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1) return cli.Exit(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
} }
bytes, err := stackitem.ToJSON(result) bytes, err := stackitem.ToJSON(result)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Sprintf("failed to convert result to JSON: %s", err), 1) return cli.Exit(fmt.Sprintf("failed to convert result to JSON: %s", err), 1)
} }
fmt.Fprintln(ctx.App.Writer, string(bytes)) fmt.Fprintln(ctx.App.Writer, string(bytes))
return nil return nil

View file

@ -27,7 +27,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
// transferTarget represents target address, token amount and data for transfer. // transferTarget represents target address, token amount and data for transfer.
@ -39,7 +39,7 @@ type transferTarget struct {
} }
var ( var (
tokenFlag = cli.StringFlag{ tokenFlag = &cli.StringFlag{
Name: "token", Name: "token",
Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))", Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))",
} }
@ -47,17 +47,19 @@ var (
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
tokenFlag, tokenFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Aliases: []string{"a"},
Usage: "Address to use", Usage: "Address to use",
}, },
} }
importFlags = append([]cli.Flag{ importFlags = append([]cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "token", Name: "token",
Usage: "Token contract address or hash in LE", Usage: "Token contract address or hash in LE",
Required: true,
}, },
}, options.RPC...) }, options.RPC...)
baseTransferFlags = []cli.Flag{ baseTransferFlags = []cli.Flag{
@ -71,7 +73,7 @@ var (
txctx.SysGasFlag, txctx.SysGasFlag,
txctx.ForceFlag, txctx.ForceFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
cli.StringFlag{ &cli.StringFlag{
Name: "amount", Name: "amount",
Usage: "Amount of asset to send", Usage: "Amount of asset to send",
}, },
@ -88,17 +90,17 @@ var (
}, options.RPC...) }, options.RPC...)
) )
func newNEP17Commands() []cli.Command { func newNEP17Commands() []*cli.Command {
balanceFlags := make([]cli.Flag, len(baseBalanceFlags)) balanceFlags := make([]cli.Flag, len(baseBalanceFlags))
copy(balanceFlags, baseBalanceFlags) copy(balanceFlags, baseBalanceFlags)
balanceFlags = append(balanceFlags, options.RPC...) balanceFlags = append(balanceFlags, options.RPC...)
transferFlags := make([]cli.Flag, len(baseTransferFlags)) transferFlags := make([]cli.Flag, len(baseTransferFlags))
copy(transferFlags, baseTransferFlags) copy(transferFlags, baseTransferFlags)
transferFlags = append(transferFlags, options.RPC...) transferFlags = append(transferFlags, options.RPC...)
return []cli.Command{ return []*cli.Command{
{ {
Name: "balance", Name: "balance",
Usage: "get address balance", Usage: "Get address balance",
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>]", UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>]",
Description: `Prints NEP-17 balances for address and tokens specified. By default (no Description: `Prints NEP-17 balances for address and tokens specified. By default (no
address or token parameter) all tokens for all accounts in the specified wallet address or token parameter) all tokens for all accounts in the specified wallet
@ -118,14 +120,14 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "import", Name: "import",
Usage: "import NEP-17 token to a wallet", Usage: "Import NEP-17 token to a wallet",
UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --token <hash>", UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] --token <hash>",
Action: importNEP17Token, Action: importNEP17Token,
Flags: importFlags, Flags: importFlags,
}, },
{ {
Name: "info", Name: "info",
Usage: "print imported NEP-17 token info", Usage: "Print imported NEP-17 token info",
UsageText: "print -w wallet [--wallet-config path] [--token <hash-or-name>]", UsageText: "print -w wallet [--wallet-config path] [--token <hash-or-name>]",
Action: printNEP17Info, Action: printNEP17Info,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -136,7 +138,7 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "remove", Name: "remove",
Usage: "remove NEP-17 token from the wallet", Usage: "Remove NEP-17 token from the wallet",
UsageText: "remove -w wallet [--wallet-config path] --token <hash-or-name>", UsageText: "remove -w wallet [--wallet-config path] --token <hash-or-name>",
Action: removeNEP17Token, Action: removeNEP17Token,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -148,8 +150,8 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "transfer", Name: "transfer",
Usage: "transfer NEP-17 tokens", Usage: "Transfer NEP-17 tokens",
UsageText: "transfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]", UsageText: "transfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> [--timeout <time>] --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
Action: transferNEP17, Action: transferNEP17,
Flags: transferFlags, Flags: transferFlags,
Description: `Transfers specified NEP-17 token amount with optional 'data' parameter and cosigners Description: `Transfers specified NEP-17 token amount with optional 'data' parameter and cosigners
@ -163,7 +165,7 @@ func newNEP17Commands() []cli.Command {
}, },
{ {
Name: "multitransfer", Name: "multitransfer",
Usage: "transfer NEP-17 tokens to multiple recipients", Usage: "Transfer NEP-17 tokens to multiple recipients",
UsageText: `multitransfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr>` + UsageText: `multitransfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr>` +
` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]] [-- <cosigner1:Scope> [<cosigner2> [...]]]`, ` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]] [-- <cosigner1:Scope> [<cosigner2> [...]]]`,
Action: multiTransferNEP17, Action: multiTransferNEP17,
@ -222,7 +224,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
} }
wall, _, err := readWallet(ctx) wall, _, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("bad wallet: %w", err), 1) return cli.Exit(fmt.Errorf("bad wallet: %w", err), 1)
} }
defer wall.Close() defer wall.Close()
@ -231,12 +233,12 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
addrHash := addrFlag.Uint160() addrHash := addrFlag.Uint160()
acc := wall.GetAccount(addrHash) acc := wall.GetAccount(addrHash)
if acc == nil { if acc == nil {
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addrHash)), 1) return cli.Exit(fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addrHash)), 1)
} }
accounts = append(accounts, acc) accounts = append(accounts, acc)
} else { } else {
if len(wall.Accounts) == 0 { if len(wall.Accounts) == 0 {
return cli.NewExitError(errors.New("no accounts in the wallet"), 1) return cli.Exit(errors.New("no accounts in the wallet"), 1)
} }
accounts = wall.Accounts accounts = wall.Accounts
} }
@ -246,7 +248,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
name := ctx.String("token") name := ctx.String("token")
@ -274,7 +276,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
// But if we have an exact hash, it must be correct. // But if we have an exact hash, it must be correct.
token, err = getTokenWithStandard(c, h, standard) token, err = getTokenWithStandard(c, h, standard)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("%q is not a valid %s token: %w", name, standard, err), 1) return cli.Exit(fmt.Errorf("%q is not a valid %s token: %w", name, standard, err), 1)
} }
} }
} }
@ -284,7 +286,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
if len(tokenID) > 0 { if len(tokenID) > 0 {
_, err = hex.DecodeString(tokenID) _, err = hex.DecodeString(tokenID)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", err), 1) return cli.Exit(fmt.Errorf("invalid token ID: %w", err), 1)
} }
} }
} }
@ -296,7 +298,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
err = accHandler(ctx, c, acc.ScriptHash(), name, token, tokenID) err = accHandler(ctx, c, acc.ScriptHash(), name, token, tokenID)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
} }
return nil return nil
@ -388,20 +390,17 @@ func importNEPToken(ctx *cli.Context, standard string) error {
} }
wall, _, err := openWallet(ctx, true) wall, _, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
tokenHashFlag := ctx.Generic("token").(*flags.Address) tokenHashFlag := ctx.Generic("token").(*flags.Address)
if !tokenHashFlag.IsSet {
return cli.NewExitError("token contract hash was not set", 1)
}
tokenHash := tokenHashFlag.Uint160() tokenHash := tokenHashFlag.Uint160()
for _, t := range wall.Extra.Tokens { for _, t := range wall.Extra.Tokens {
if t.Hash.Equals(tokenHash) && t.Standard == standard { if t.Hash.Equals(tokenHash) && t.Standard == standard {
printTokenInfo(ctx, t) printTokenInfo(ctx, t)
return cli.NewExitError(fmt.Errorf("%s token already exists", standard), 1) return cli.Exit(fmt.Errorf("%s token already exists", standard), 1)
} }
} }
@ -410,17 +409,17 @@ func importNEPToken(ctx *cli.Context, standard string) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
tok, err := getTokenWithStandard(c, tokenHash, standard) tok, err := getTokenWithStandard(c, tokenHash, standard)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't receive token info: %w", err), 1) return cli.Exit(fmt.Errorf("can't receive token info: %w", err), 1)
} }
wall.AddToken(tok) wall.AddToken(tok)
if err := wall.Save(); err != nil { if err := wall.Save(); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
printTokenInfo(ctx, tok) printTokenInfo(ctx, tok)
return nil return nil
@ -457,14 +456,14 @@ func printNEPInfo(ctx *cli.Context, standard string) error {
} }
wall, _, err := readWallet(ctx) wall, _, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
if name := ctx.String("token"); name != "" { if name := ctx.String("token"); name != "" {
token, err := getMatchingToken(ctx, wall, name, standard) token, err := getMatchingToken(ctx, wall, name, standard)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
printTokenInfo(ctx, token) printTokenInfo(ctx, token)
return nil return nil
@ -493,13 +492,13 @@ func removeNEPToken(ctx *cli.Context, standard string) error {
} }
wall, _, err := openWallet(ctx, true) wall, _, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
token, err := getMatchingToken(ctx, wall, ctx.String("token"), standard) token, err := getMatchingToken(ctx, wall, ctx.String("token"), standard)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if !ctx.Bool("force") { if !ctx.Bool("force") {
if ok := askForConsent(ctx.App.Writer); !ok { if ok := askForConsent(ctx.App.Writer); !ok {
@ -507,9 +506,9 @@ func removeNEPToken(ctx *cli.Context, standard string) error {
} }
} }
if err := wall.RemoveToken(token.Hash); err != nil { if err := wall.RemoveToken(token.Hash); err != nil {
return cli.NewExitError(fmt.Errorf("can't remove token: %w", err), 1) return cli.Exit(fmt.Errorf("can't remove token: %w", err), 1)
} else if err := wall.Save(); err != nil { } else if err := wall.Save(); err != nil {
return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1) return cli.Exit(fmt.Errorf("error while saving wallet: %w", err), 1)
} }
return nil return nil
} }
@ -517,25 +516,25 @@ func removeNEPToken(ctx *cli.Context, standard string) error {
func multiTransferNEP17(ctx *cli.Context) error { func multiTransferNEP17(ctx *cli.Context) error {
wall, pass, err := readWallet(ctx) wall, pass, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
fromFlag := ctx.Generic("from").(*flags.Address) fromFlag := ctx.Generic("from").(*flags.Address)
from, err := getDefaultAddress(fromFlag, wall) from, err := getDefaultAddress(fromFlag, wall)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
acc, err := options.GetUnlockedAccount(wall, from, pass) acc, err := options.GetUnlockedAccount(wall, from, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
defer cancel() defer cancel()
if ctx.NArg() == 0 { if ctx.NArg() == 0 {
return cli.NewExitError("empty recipients list", 1) return cli.Exit("empty recipients list", 1)
} }
var ( var (
recipients []transferTarget recipients []transferTarget
@ -554,7 +553,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
} }
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry) signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
} }
c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts) c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts)
if exitErr != nil { if exitErr != nil {
@ -566,7 +565,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
arg := ctx.Args().Get(i) arg := ctx.Args().Get(i)
ss := strings.SplitN(arg, ":", 3) ss := strings.SplitN(arg, ":", 3)
if len(ss) != 3 { if len(ss) != 3 {
return cli.NewExitError("send format must be '<token>:<addr>:<amount>", 1) return cli.Exit("send format must be '<token>:<addr>:<amount>", 1)
} }
token, ok := cache[ss[0]] token, ok := cache[ss[0]]
if !ok { if !ok {
@ -574,18 +573,18 @@ func multiTransferNEP17(ctx *cli.Context) error {
if err != nil { if err != nil {
token, err = getMatchingTokenRPC(ctx, c, from, ss[0], manifest.NEP17StandardName) token, err = getMatchingTokenRPC(ctx, c, from, ss[0], manifest.NEP17StandardName)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1) return cli.Exit(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
} }
} }
} }
cache[ss[0]] = token cache[ss[0]] = token
addr, err := address.StringToUint160(ss[1]) addr, err := address.StringToUint160(ss[1])
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid address: '%s'", ss[1]), 1) return cli.Exit(fmt.Errorf("invalid address: '%s'", ss[1]), 1)
} }
amount, err := fixedn.FromString(ss[2], int(token.Decimals)) amount, err := fixedn.FromString(ss[2], int(token.Decimals))
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1) return cli.Exit(fmt.Errorf("invalid amount: %w", err), 1)
} }
recipients = append(recipients, transferTarget{ recipients = append(recipients, transferTarget{
Token: token.Hash, Token: token.Hash,
@ -597,7 +596,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
tx, err := makeMultiTransferNEP17(act, recipients) tx, err := makeMultiTransferNEP17(act, recipients)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) return cli.Exit(fmt.Errorf("can't make transaction: %w", err), 1)
} }
return txctx.SignAndSend(ctx, act, acc, tx) return txctx.SignAndSend(ctx, act, acc, tx)
} }
@ -611,18 +610,18 @@ func transferNEP(ctx *cli.Context, standard string) error {
wall, pass, err := readWallet(ctx) wall, pass, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
fromFlag := ctx.Generic("from").(*flags.Address) fromFlag := ctx.Generic("from").(*flags.Address)
from, err := getDefaultAddress(fromFlag, wall) from, err := getDefaultAddress(fromFlag, wall)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
acc, err := options.GetUnlockedAccount(wall, from, pass) acc, err := options.GetUnlockedAccount(wall, from, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -638,7 +637,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
} }
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry) signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
} }
c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts) c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts)
@ -647,15 +646,12 @@ func transferNEP(ctx *cli.Context, standard string) error {
} }
toFlag := ctx.Generic("to").(*flags.Address) toFlag := ctx.Generic("to").(*flags.Address)
if !toFlag.IsSet {
return cli.NewExitError(errors.New("missing receiver address (--to)"), 1)
}
to := toFlag.Uint160() to := toFlag.Uint160()
token, err := getMatchingToken(ctx, wall, ctx.String("token"), standard) token, err := getMatchingToken(ctx, wall, ctx.String("token"), standard)
if err != nil { if err != nil {
token, err = getMatchingTokenRPC(ctx, c, from, ctx.String("token"), standard) token, err = getMatchingTokenRPC(ctx, c, from, ctx.String("token"), standard)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1) return cli.Exit(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
} }
} }
@ -663,7 +659,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
amount, err := fixedn.FromString(amountArg, int(token.Decimals)) amount, err := fixedn.FromString(amountArg, int(token.Decimals))
// It's OK for NEP-11 transfer to not have amount set. // It's OK for NEP-11 transfer to not have amount set.
if err != nil && (standard == manifest.NEP17StandardName || amountArg != "") { if err != nil && (standard == manifest.NEP17StandardName || amountArg != "") {
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1) return cli.Exit(fmt.Errorf("invalid amount: %w", err), 1)
} }
switch standard { switch standard {
case manifest.NEP17StandardName: case manifest.NEP17StandardName:
@ -672,11 +668,11 @@ func transferNEP(ctx *cli.Context, standard string) error {
case manifest.NEP11StandardName: case manifest.NEP11StandardName:
tokenID := ctx.String("id") tokenID := ctx.String("id")
if tokenID == "" { if tokenID == "" {
return cli.NewExitError(errors.New("token ID should be specified"), 1) return cli.Exit(errors.New("token ID should be specified"), 1)
} }
tokenIDBytes, terr := hex.DecodeString(tokenID) tokenIDBytes, terr := hex.DecodeString(tokenID)
if terr != nil { if terr != nil {
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", terr), 1) return cli.Exit(fmt.Errorf("invalid token ID: %w", terr), 1)
} }
if amountArg == "" { if amountArg == "" {
n11 := nep11.NewNonDivisible(act, token.Hash) n11 := nep11.NewNonDivisible(act, token.Hash)
@ -686,10 +682,10 @@ func transferNEP(ctx *cli.Context, standard string) error {
tx, err = n11.TransferDUnsigned(act.Sender(), to, amount, tokenIDBytes, data) tx, err = n11.TransferDUnsigned(act.Sender(), to, amount, tokenIDBytes, data)
} }
default: default:
return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1) return cli.Exit(fmt.Errorf("unsupported token standard %s", standard), 1)
} }
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) return cli.Exit(fmt.Errorf("can't make transaction: %w", err), 1)
} }
return txctx.SignAndSend(ctx, act, acc, tx) return txctx.SignAndSend(ctx, act, acc, tx)

View file

@ -12,15 +12,15 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo" "github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
func newValidatorCommands() []cli.Command { func newValidatorCommands() []*cli.Command {
return []cli.Command{ return []*cli.Command{
{ {
Name: "register", Name: "register",
Usage: "register as a new candidate", Usage: "Register as a new candidate",
UsageText: "register -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]", UsageText: "register -w <path> -r <rpc> [-s timeout] -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
Action: handleRegister, Action: handleRegister,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
walletPathFlag, walletPathFlag,
@ -30,16 +30,18 @@ func newValidatorCommands() []cli.Command {
txctx.OutFlag, txctx.OutFlag,
txctx.ForceFlag, txctx.ForceFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Aliases: []string{"a"},
Required: true,
Usage: "Address to register", Usage: "Address to register",
}, },
}, options.RPC...), }, options.RPC...),
}, },
{ {
Name: "unregister", Name: "unregister",
Usage: "unregister self as a candidate", Usage: "Unregister self as a candidate",
UsageText: "unregister -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]", UsageText: "unregister -w <path> -r <rpc> [-s timeout] -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
Action: handleUnregister, Action: handleUnregister,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
walletPathFlag, walletPathFlag,
@ -49,15 +51,17 @@ func newValidatorCommands() []cli.Command {
txctx.OutFlag, txctx.OutFlag,
txctx.ForceFlag, txctx.ForceFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Required: true,
Aliases: []string{"a"},
Usage: "Address to unregister", Usage: "Address to unregister",
}, },
}, options.RPC...), }, options.RPC...),
}, },
{ {
Name: "vote", Name: "vote",
Usage: "vote for a validator", Usage: "Vote for a validator",
UsageText: "vote -w <path> -r <rpc> [-s <timeout>] [-g gas] [-e sysgas] -a <addr> [-c <public key>] [--out file] [--force] [--await]", UsageText: "vote -w <path> -r <rpc> [-s <timeout>] [-g gas] [-e sysgas] -a <addr> [-c <public key>] [--out file] [--force] [--await]",
Description: `Votes for a validator by calling "vote" method of a NEO native Description: `Votes for a validator by calling "vote" method of a NEO native
contract. Do not provide candidate argument to perform unvoting. If --await flag is contract. Do not provide candidate argument to perform unvoting. If --await flag is
@ -72,12 +76,15 @@ func newValidatorCommands() []cli.Command {
txctx.OutFlag, txctx.OutFlag,
txctx.ForceFlag, txctx.ForceFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Required: true,
Aliases: []string{"a"},
Usage: "Address to vote from", Usage: "Address to vote from",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "candidate, c", Name: "candidate",
Aliases: []string{"c"},
Usage: "Public key of candidate to vote for", Usage: "Public key of candidate to vote for",
}, },
}, options.RPC...), }, options.RPC...),
@ -103,18 +110,15 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
} }
wall, pass, err := readWallet(ctx) wall, pass, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
addrFlag := ctx.Generic("address").(*flags.Address) addrFlag := ctx.Generic("address").(*flags.Address)
if !addrFlag.IsSet {
return cli.NewExitError("address was not provided", 1)
}
addr := addrFlag.Uint160() addr := addrFlag.Uint160()
acc, err := options.GetUnlockedAccount(wall, addr, pass) acc, err := options.GetUnlockedAccount(wall, addr, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -122,7 +126,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
signers, err := cmdargs.GetSignersAccounts(acc, wall, nil, transaction.CalledByEntry) signers, err := cmdargs.GetSignersAccounts(acc, wall, nil, transaction.CalledByEntry)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
} }
_, act, exitErr := options.GetRPCWithActor(gctx, ctx, signers) _, act, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
if exitErr != nil { if exitErr != nil {
@ -132,7 +136,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
contract := neo.New(act) contract := neo.New(act)
tx, err := mkTx(contract, addr, acc) tx, err := mkTx(contract, addr, acc)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return txctx.SignAndSend(ctx, act, acc, tx) return txctx.SignAndSend(ctx, act, acc, tx)
} }

View file

@ -24,7 +24,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
const ( const (
@ -47,38 +47,43 @@ var (
) )
var ( var (
walletPathFlag = cli.StringFlag{ walletPathFlag = &cli.StringFlag{
Name: "wallet, w", Name: "wallet",
Aliases: []string{"w"},
Usage: "Path to the wallet file ('-' to read from stdin); conflicts with --wallet-config flag.", Usage: "Path to the wallet file ('-' to read from stdin); conflicts with --wallet-config flag.",
} }
walletConfigFlag = cli.StringFlag{ walletConfigFlag = &cli.StringFlag{
Name: "wallet-config", Name: "wallet-config",
Usage: "Path to the wallet config file; conflicts with --wallet flag.", Usage: "Path to the wallet config file; conflicts with --wallet flag.",
} }
wifFlag = cli.StringFlag{ wifFlag = &cli.StringFlag{
Name: "wif", Name: "wif",
Usage: "WIF to import", Usage: "WIF to import",
} }
decryptFlag = cli.BoolFlag{ decryptFlag = &cli.BoolFlag{
Name: "decrypt, d", Name: "decrypt",
Aliases: []string{"d"},
Usage: "Decrypt encrypted keys.", Usage: "Decrypt encrypted keys.",
} }
inFlag = cli.StringFlag{ inFlag = &cli.StringFlag{
Name: "in", Name: "in",
Usage: "file with JSON transaction", Required: true,
Usage: "File with JSON transaction",
Action: cmdargs.EnsureNotEmpty("in"),
} }
fromAddrFlag = flags.AddressFlag{ fromAddrFlag = &flags.AddressFlag{
Name: "from", Name: "from",
Usage: "Address to send an asset from", Usage: "Address to send an asset from",
} }
toAddrFlag = flags.AddressFlag{ toAddrFlag = &flags.AddressFlag{
Name: "to", Name: "to",
Usage: "Address to send an asset to", Usage: "Address to send an asset to",
Required: true,
} }
) )
// NewCommands returns 'wallet' command. // NewCommands returns 'wallet' command.
func NewCommands() []cli.Command { func NewCommands() []*cli.Command {
claimFlags := []cli.Flag{ claimFlags := []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
@ -87,8 +92,10 @@ func NewCommands() []cli.Command {
txctx.OutFlag, txctx.OutFlag,
txctx.ForceFlag, txctx.ForceFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Aliases: []string{"a"},
Required: true,
Usage: "Address to claim GAS for", Usage: "Address to claim GAS for",
}, },
} }
@ -99,67 +106,78 @@ func NewCommands() []cli.Command {
txctx.OutFlag, txctx.OutFlag,
txctx.AwaitFlag, txctx.AwaitFlag,
inFlag, inFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Aliases: []string{"a"},
Usage: "Address to use", Usage: "Address to use",
}, },
} }
signFlags = append(signFlags, options.RPC...) // By default, RPC flag is required. signtx may be called without provided rpc-endpoint.
return []cli.Command{{ rpcFlagOriginal, _ := options.RPC[0].(*cli.StringFlag)
rpcFlag := *rpcFlagOriginal
rpcFlag.Required = false
signFlags = append(signFlags, &rpcFlag)
signFlags = append(signFlags, options.RPC[1:]...)
return []*cli.Command{{
Name: "wallet", Name: "wallet",
Usage: "create, open and manage a Neo wallet", Usage: "Create, open and manage a Neo wallet",
Subcommands: []cli.Command{ Subcommands: []*cli.Command{
{ {
Name: "claim", Name: "claim",
Usage: "claim GAS", Usage: "Claim GAS",
UsageText: "neo-go wallet claim -w wallet [--wallet-config path] [-g gas] [-e sysgas] -a address -r endpoint [-s timeout] [--out file] [--force] [--await]", UsageText: "neo-go wallet claim -w wallet [--wallet-config path] [-g gas] [-e sysgas] -a address -r endpoint [-s timeout] [--out file] [--force] [--await]",
Action: claimGas, Action: claimGas,
Flags: claimFlags, Flags: claimFlags,
}, },
{ {
Name: "init", Name: "init",
Usage: "create a new wallet", Usage: "Create a new wallet",
UsageText: "neo-go wallet init -w wallet [--wallet-config path] [-a]", UsageText: "neo-go wallet init -w wallet [--wallet-config path] [-a]",
Action: createWallet, Action: createWallet,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
cli.BoolFlag{ &cli.BoolFlag{
Name: "account, a", Name: "account",
Aliases: []string{"a"},
Usage: "Create a new account", Usage: "Create a new account",
}, },
}, },
}, },
{ {
Name: "change-password", Name: "change-password",
Usage: "change password for accounts", Usage: "Change password for accounts",
UsageText: "neo-go wallet change-password -w wallet -a address", UsageText: "neo-go wallet change-password -w wallet [-a address]",
Action: changePassword, Action: changePassword,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Usage: "address to change password for", Aliases: []string{"a"},
Usage: "Address to change password for",
}, },
}, },
}, },
{ {
Name: "convert", Name: "convert",
Usage: "convert addresses from existing Neo Legacy NEP6-wallet to Neo N3 format", Usage: "Convert addresses from existing Neo Legacy NEP6-wallet to Neo N3 format",
UsageText: "neo-go wallet convert -w legacywallet [--wallet-config path] -o n3wallet", UsageText: "neo-go wallet convert -w legacywallet [--wallet-config path] -o n3wallet",
Action: convertWallet, Action: convertWallet,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
cli.StringFlag{ &cli.StringFlag{
Name: "out, o", Name: "out",
Usage: "where to write converted wallet", Aliases: []string{"o"},
Required: true,
Usage: "Where to write converted wallet",
Action: cmdargs.EnsureNotEmpty("out"),
}, },
}, },
}, },
{ {
Name: "create", Name: "create",
Usage: "add an account to the existing wallet", Usage: "Add an account to the existing wallet",
UsageText: "neo-go wallet create -w wallet [--wallet-config path]", UsageText: "neo-go wallet create -w wallet [--wallet-config path]",
Action: addAccount, Action: addAccount,
Flags: []cli.Flag{ Flags: []cli.Flag{
@ -169,7 +187,7 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "dump", Name: "dump",
Usage: "check and dump an existing Neo wallet", Usage: "Check and dump an existing Neo wallet",
UsageText: "neo-go wallet dump -w wallet [--wallet-config path] [-d]", UsageText: "neo-go wallet dump -w wallet [--wallet-config path] [-d]",
Description: `Prints the given wallet (via -w option or via wallet configuration file) in JSON Description: `Prints the given wallet (via -w option or via wallet configuration file) in JSON
format to the standard output. If -d is given, private keys are unencrypted and format to the standard output. If -d is given, private keys are unencrypted and
@ -185,21 +203,22 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "dump-keys", Name: "dump-keys",
Usage: "dump public keys for account", Usage: "Dump public keys for account",
UsageText: "neo-go wallet dump-keys -w wallet [--wallet-config path] [-a address]", UsageText: "neo-go wallet dump-keys -w wallet [--wallet-config path] [-a address]",
Action: dumpKeys, Action: dumpKeys,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Usage: "address to print public keys for", Aliases: []string{"a"},
Usage: "Address to print public keys for",
}, },
}, },
}, },
{ {
Name: "export", Name: "export",
Usage: "export keys for address", Usage: "Export keys for address",
UsageText: "export -w wallet [--wallet-config path] [--decrypt] [<address>]", UsageText: "export -w wallet [--wallet-config path] [--decrypt] [<address>]",
Description: `Prints the key for the given account to the standard output. It uses NEP-2 Description: `Prints the key for the given account to the standard output. It uses NEP-2
encrypted format by default (the way NEP-6 wallets store it) or WIF format if encrypted format by default (the way NEP-6 wallets store it) or WIF format if
@ -216,18 +235,19 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "import", Name: "import",
Usage: "import WIF of a standard signature contract", Usage: "Import WIF of a standard signature contract",
UsageText: "import -w wallet [--wallet-config path] --wif <wif> [--name <account_name>]", UsageText: "import -w wallet [--wallet-config path] --wif <wif> [--name <account_name>]",
Action: importWallet, Action: importWallet,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
wifFlag, wifFlag,
cli.StringFlag{ &cli.StringFlag{
Name: "name, n", Name: "name",
Aliases: []string{"n"},
Usage: "Optional account name", Usage: "Optional account name",
}, },
cli.StringFlag{ &cli.StringFlag{
Name: "contract", Name: "contract",
Usage: "Verification script for custom contracts", Usage: "Verification script for custom contracts",
}, },
@ -235,7 +255,7 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "import-multisig", Name: "import-multisig",
Usage: "import multisig contract", Usage: "Import multisig contract",
UsageText: "import-multisig -w wallet [--wallet-config path] [--wif <wif>] [--name <account_name>] --min <m>" + UsageText: "import-multisig -w wallet [--wallet-config path] [--wif <wif>] [--name <account_name>] --min <m>" +
" [<pubkey1> [<pubkey2> [...]]]", " [<pubkey1> [<pubkey2> [...]]]",
Description: `Imports a standard multisignature contract with "m out of n" signatures required where "m" is Description: `Imports a standard multisignature contract with "m out of n" signatures required where "m" is
@ -250,53 +270,60 @@ func NewCommands() []cli.Command {
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
wifFlag, wifFlag,
cli.StringFlag{ &cli.StringFlag{
Name: "name, n", Name: "name",
Aliases: []string{"n"},
Usage: "Optional account name", Usage: "Optional account name",
}, },
cli.IntFlag{ &cli.IntFlag{
Name: "min, m", Name: "min",
Aliases: []string{"m"},
Usage: "Minimal number of signatures", Usage: "Minimal number of signatures",
}, },
}, },
}, },
{ {
Name: "import-deployed", Name: "import-deployed",
Usage: "import deployed contract", Usage: "Import deployed contract",
UsageText: "import-deployed -w wallet [--wallet-config path] --wif <wif> --contract <hash> [--name <account_name>]", UsageText: "import-deployed -w wallet [--wallet-config path] --wif <wif> --contract <hash> --rpc-endpoint <endpoint> [-s <timeout>] [--name <account_name>]",
Action: importDeployed, Action: importDeployed,
Flags: append([]cli.Flag{ Flags: append([]cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
wifFlag, wifFlag,
cli.StringFlag{ &cli.StringFlag{
Name: "name, n", Name: "name",
Aliases: []string{"n"},
Usage: "Optional account name", Usage: "Optional account name",
}, },
flags.AddressFlag{ &flags.AddressFlag{
Name: "contract, c", Name: "contract",
Aliases: []string{"c"},
Required: true,
Usage: "Contract hash or address", Usage: "Contract hash or address",
}, },
}, options.RPC...), }, options.RPC...),
}, },
{ {
Name: "remove", Name: "remove",
Usage: "remove an account from the wallet", Usage: "Remove an account from the wallet",
UsageText: "remove -w wallet [--wallet-config path] [--force] --address <addr>", UsageText: "remove -w wallet [--wallet-config path] [--force] --address <addr>",
Action: removeAccount, Action: removeAccount,
Flags: []cli.Flag{ Flags: []cli.Flag{
walletPathFlag, walletPathFlag,
walletConfigFlag, walletConfigFlag,
txctx.ForceFlag, txctx.ForceFlag,
flags.AddressFlag{ &flags.AddressFlag{
Name: "address, a", Name: "address",
Aliases: []string{"a"},
Required: true,
Usage: "Account address or hash in LE form to be removed", Usage: "Account address or hash in LE form to be removed",
}, },
}, },
}, },
{ {
Name: "sign", Name: "sign",
Usage: "cosign transaction with multisig/contract/additional account", Usage: "Cosign transaction with multisig/contract/additional account",
UsageText: "sign -w wallet [--wallet-config path] --address <address> --in <file.in> [--out <file.out>] [-r <endpoint>] [--await]", UsageText: "sign -w wallet [--wallet-config path] --address <address> --in <file.in> [--out <file.out>] [-r <endpoint>] [--await]",
Description: `Signs the given (in file.in) context (which must be a transaction Description: `Signs the given (in file.in) context (which must be a transaction
signing context) for the given address using the given wallet. This command can signing context) for the given address using the given wallet. This command can
@ -312,7 +339,7 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "strip-keys", Name: "strip-keys",
Usage: "remove private keys for all accounts", Usage: "Remove private keys for all accounts",
UsageText: "neo-go wallet strip-keys -w wallet [--wallet-config path] [--force]", UsageText: "neo-go wallet strip-keys -w wallet [--wallet-config path] [--force]",
Description: `Removes private keys for all accounts from the given wallet. Notice, Description: `Removes private keys for all accounts from the given wallet. Notice,
this is a very dangerous action (you can lose keys if you don't have a wallet this is a very dangerous action (you can lose keys if you don't have a wallet
@ -329,17 +356,17 @@ func NewCommands() []cli.Command {
}, },
{ {
Name: "nep17", Name: "nep17",
Usage: "work with NEP-17 contracts", Usage: "Work with NEP-17 contracts",
Subcommands: newNEP17Commands(), Subcommands: newNEP17Commands(),
}, },
{ {
Name: "nep11", Name: "nep11",
Usage: "work with NEP-11 contracts", Usage: "Work with NEP-11 contracts",
Subcommands: newNEP11Commands(), Subcommands: newNEP11Commands(),
}, },
{ {
Name: "candidate", Name: "candidate",
Usage: "work with candidates", Usage: "Work with candidates",
Subcommands: newValidatorCommands(), Subcommands: newValidatorCommands(),
}, },
}, },
@ -358,24 +385,24 @@ func changePassword(ctx *cli.Context) error {
} }
wall, _, err := openWallet(ctx, false) wall, _, err := openWallet(ctx, false)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
if len(wall.Accounts) == 0 { if len(wall.Accounts) == 0 {
return cli.NewExitError("wallet has no accounts", 1) return cli.Exit("wallet has no accounts", 1)
} }
addrFlag := ctx.Generic("address").(*flags.Address) addrFlag := ctx.Generic("address").(*flags.Address)
if addrFlag.IsSet { if addrFlag.IsSet {
// Check for account presence first before asking for password. // Check for account presence first before asking for password.
acc := wall.GetAccount(addrFlag.Uint160()) acc := wall.GetAccount(addrFlag.Uint160())
if acc == nil { if acc == nil {
return cli.NewExitError("account is missing", 1) return cli.Exit("account is missing", 1)
} }
} }
oldPass, err := input.ReadPassword(EnterOldPasswordPrompt) oldPass, err := input.ReadPassword(EnterOldPasswordPrompt)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("Error reading old password: %w", err), 1) return cli.Exit(fmt.Errorf("error reading old password: %w", err), 1)
} }
for i := range wall.Accounts { for i := range wall.Accounts {
@ -384,13 +411,13 @@ func changePassword(ctx *cli.Context) error {
} }
err := wall.Accounts[i].Decrypt(oldPass, wall.Scrypt) err := wall.Accounts[i].Decrypt(oldPass, wall.Scrypt)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("unable to decrypt account %s: %w", wall.Accounts[i].Address, err), 1) return cli.Exit(fmt.Errorf("unable to decrypt account %s: %w", wall.Accounts[i].Address, err), 1)
} }
} }
pass, err := readNewPassword() pass, err := readNewPassword()
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("Error reading new password: %w", err), 1) return cli.Exit(fmt.Errorf("error reading new password: %w", err), 1)
} }
for i := range wall.Accounts { for i := range wall.Accounts {
if addrFlag.IsSet && wall.Accounts[i].Address != addrFlag.String() { if addrFlag.IsSet && wall.Accounts[i].Address != addrFlag.String() {
@ -398,12 +425,12 @@ func changePassword(ctx *cli.Context) error {
} }
err := wall.Accounts[i].Encrypt(pass, wall.Scrypt) err := wall.Accounts[i].Encrypt(pass, wall.Scrypt)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
} }
err = wall.Save() err = wall.Save()
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("Error saving the wallet: %w", err), 1) return cli.Exit(fmt.Errorf("error saving the wallet: %w", err), 1)
} }
return nil return nil
} }
@ -414,16 +441,13 @@ func convertWallet(ctx *cli.Context) error {
} }
wall, pass, err := newWalletV2FromFile(ctx.String("wallet"), ctx.String("wallet-config")) wall, pass, err := newWalletV2FromFile(ctx.String("wallet"), ctx.String("wallet-config"))
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
out := ctx.String("out") out := ctx.String("out")
if len(out) == 0 {
return cli.NewExitError("missing out path", 1)
}
newWallet, err := wallet.NewWallet(out) newWallet, err := wallet.NewWallet(out)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
newWallet.Scrypt = wall.Scrypt newWallet.Scrypt = wall.Scrypt
@ -431,19 +455,19 @@ func convertWallet(ctx *cli.Context) error {
if len(wall.Accounts) != 1 || pass == nil { if len(wall.Accounts) != 1 || pass == nil {
password, err := input.ReadPassword(fmt.Sprintf("Enter password for account %s (label '%s') > ", acc.Address, acc.Label)) password, err := input.ReadPassword(fmt.Sprintf("Enter password for account %s (label '%s') > ", acc.Address, acc.Label))
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("Error reading password: %w", err), 1) return cli.Exit(fmt.Errorf("error reading password: %w", err), 1)
} }
pass = &password pass = &password
} }
newAcc, err := acc.convert(*pass, wall.Scrypt) newAcc, err := acc.convert(*pass, wall.Scrypt)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
newWallet.AddAccount(newAcc) newWallet.AddAccount(newAcc)
} }
if err := newWallet.Save(); err != nil { if err := newWallet.Save(); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
} }
@ -454,12 +478,12 @@ func addAccount(ctx *cli.Context) error {
} }
wall, pass, err := openWallet(ctx, true) wall, pass, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
if err := createAccount(wall, pass); err != nil { if err := createAccount(wall, pass); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
@ -468,7 +492,7 @@ func addAccount(ctx *cli.Context) error {
func exportKeys(ctx *cli.Context) error { func exportKeys(ctx *cli.Context) error {
wall, pass, err := readWallet(ctx) wall, pass, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
@ -476,13 +500,13 @@ func exportKeys(ctx *cli.Context) error {
decrypt := ctx.Bool("decrypt") decrypt := ctx.Bool("decrypt")
if ctx.NArg() == 0 && decrypt { if ctx.NArg() == 0 && decrypt {
return cli.NewExitError(errors.New("address must be provided if '--decrypt' flag is used"), 1) return cli.Exit(errors.New("address must be provided if '--decrypt' flag is used"), 1)
} else if ctx.NArg() > 0 { } else if ctx.NArg() > 0 {
// check address format just to catch possible typos // check address format just to catch possible typos
addr = ctx.Args().First() addr = ctx.Args().First()
_, err := address.StringToUint160(addr) _, err := address.StringToUint160(addr)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't parse address: %w", err), 1) return cli.Exit(fmt.Errorf("can't parse address: %w", err), 1)
} }
} }
@ -508,14 +532,14 @@ loop:
if pass == nil { if pass == nil {
password, err := input.ReadPassword(EnterPasswordPrompt) password, err := input.ReadPassword(EnterPasswordPrompt)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("Error reading password: %w", err), 1) return cli.Exit(fmt.Errorf("error reading password: %w", err), 1)
} }
pass = &password pass = &password
} }
pk, err := keys.NEP2Decrypt(wif, *pass, wall.Scrypt) pk, err := keys.NEP2Decrypt(wif, *pass, wall.Scrypt)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
wif = pk.WIF() wif = pk.WIF()
@ -536,22 +560,22 @@ func importMultisig(ctx *cli.Context) error {
wall, pass, err := openWallet(ctx, true) wall, pass, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
m := ctx.Int("min") m := ctx.Int("min")
if ctx.NArg() < m { if ctx.NArg() < m {
return cli.NewExitError(errors.New("insufficient number of public keys"), 1) return cli.Exit(errors.New("insufficient number of public keys"), 1)
} }
args := []string(ctx.Args()) args := ctx.Args().Slice()
pubs := make([]*keys.PublicKey, len(args)) pubs := make([]*keys.PublicKey, len(args))
for i := range args { for i := range args {
pubs[i], err = keys.NewPublicKeyFromString(args[i]) pubs[i], err = keys.NewPublicKeyFromString(args[i])
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't decode public key %d: %w", i, err), 1) return cli.Exit(fmt.Errorf("can't decode public key %d: %w", i, err), 1)
} }
} }
@ -579,31 +603,31 @@ loop:
if acc != nil { if acc != nil {
err = acc.ConvertMultisigEncrypted(accPub, m, pubs) err = acc.ConvertMultisigEncrypted(accPub, m, pubs)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if label != nil { if label != nil {
acc.Label = *label acc.Label = *label
} }
if err := addAccountAndSave(wall, acc); err != nil { if err := addAccountAndSave(wall, acc); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
} }
if !ctx.IsSet("wif") { if !ctx.IsSet("wif") {
return cli.NewExitError(errors.New("none of the provided public keys correspond to an existing key in the wallet or multiple matching accounts found in the wallet, and no WIF is provided"), 1) return cli.Exit(errors.New("none of the provided public keys correspond to an existing key in the wallet or multiple matching accounts found in the wallet, and no WIF is provided"), 1)
} }
acc, err = newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass) acc, err = newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if err := acc.ConvertMultisig(m, pubs); err != nil { if err := acc.ConvertMultisig(m, pubs); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if err := addAccountAndSave(wall, acc); err != nil { if err := addAccountAndSave(wall, acc); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
@ -615,14 +639,11 @@ func importDeployed(ctx *cli.Context) error {
} }
wall, pass, err := openWallet(ctx, true) wall, pass, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
rawHash := ctx.Generic("contract").(*flags.Address) rawHash := ctx.Generic("contract").(*flags.Address)
if !rawHash.IsSet {
return cli.NewExitError("contract hash was not provided", 1)
}
var label *string var label *string
if ctx.IsSet("name") { if ctx.IsSet("name") {
@ -631,7 +652,7 @@ func importDeployed(ctx *cli.Context) error {
} }
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass) acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
gctx, cancel := options.GetTimeoutContext(ctx) gctx, cancel := options.GetTimeoutContext(ctx)
@ -639,16 +660,16 @@ func importDeployed(ctx *cli.Context) error {
c, err := options.GetRPCClient(gctx, ctx) c, err := options.GetRPCClient(gctx, ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
cs, err := c.GetContractStateByHash(rawHash.Uint160()) cs, err := c.GetContractStateByHash(rawHash.Uint160())
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1) return cli.Exit(fmt.Errorf("can't fetch contract info: %w", err), 1)
} }
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1) md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1)
if md == nil || md.ReturnType != smartcontract.BoolType { if md == nil || md.ReturnType != smartcontract.BoolType {
return cli.NewExitError("contract has no `verify` method with boolean return", 1) return cli.Exit("contract has no `verify` method with boolean return", 1)
} }
acc.Address = address.Uint160ToString(cs.Hash) acc.Address = address.Uint160ToString(cs.Hash)
// Explicitly overwrite single signature script of the provided WIF since the contract is known to be deployed. // Explicitly overwrite single signature script of the provided WIF since the contract is known to be deployed.
@ -663,7 +684,7 @@ func importDeployed(ctx *cli.Context) error {
acc.Contract.Deployed = true acc.Contract.Deployed = true
if err := addAccountAndSave(wall, acc); err != nil { if err := addAccountAndSave(wall, acc); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
@ -675,7 +696,7 @@ func importWallet(ctx *cli.Context) error {
} }
wall, pass, err := openWallet(ctx, true) wall, pass, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
@ -687,19 +708,19 @@ func importWallet(ctx *cli.Context) error {
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass) acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if ctrFlag := ctx.String("contract"); ctrFlag != "" { if ctrFlag := ctx.String("contract"); ctrFlag != "" {
ctr, err := hex.DecodeString(ctrFlag) ctr, err := hex.DecodeString(ctrFlag)
if err != nil { if err != nil {
return cli.NewExitError("invalid contract", 1) return cli.Exit("invalid contract", 1)
} }
acc.Contract.Script = ctr acc.Contract.Script = ctr
} }
if err := addAccountAndSave(wall, acc); err != nil { if err := addAccountAndSave(wall, acc); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
return nil return nil
@ -711,17 +732,14 @@ func removeAccount(ctx *cli.Context) error {
} }
wall, _, err := openWallet(ctx, true) wall, _, err := openWallet(ctx, true)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
addr := ctx.Generic("address").(*flags.Address) addr := ctx.Generic("address").(*flags.Address)
if !addr.IsSet {
return cli.NewExitError("valid account address must be provided", 1)
}
acc := wall.GetAccount(addr.Uint160()) acc := wall.GetAccount(addr.Uint160())
if acc == nil { if acc == nil {
return cli.NewExitError("account wasn't found", 1) return cli.Exit("account wasn't found", 1)
} }
if !ctx.Bool("force") { if !ctx.Bool("force") {
@ -732,10 +750,10 @@ func removeAccount(ctx *cli.Context) error {
} }
if err := wall.RemoveAccount(acc.Address); err != nil { if err := wall.RemoveAccount(acc.Address); err != nil {
return cli.NewExitError(fmt.Errorf("error on remove: %w", err), 1) return cli.Exit(fmt.Errorf("error on remove: %w", err), 1)
} }
if err := wall.Save(); err != nil { if err := wall.Save(); err != nil {
return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1) return cli.Exit(fmt.Errorf("error while saving wallet: %w", err), 1)
} }
return nil return nil
} }
@ -758,14 +776,14 @@ func dumpWallet(ctx *cli.Context) error {
} }
wall, pass, err := readWallet(ctx) wall, pass, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
if ctx.Bool("decrypt") { if ctx.Bool("decrypt") {
if pass == nil { if pass == nil {
password, err := input.ReadPassword(EnterPasswordPrompt) password, err := input.ReadPassword(EnterPasswordPrompt)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("Error reading password: %w", err), 1) return cli.Exit(fmt.Errorf("error reading password: %w", err), 1)
} }
pass = &password pass = &password
} }
@ -773,7 +791,7 @@ func dumpWallet(ctx *cli.Context) error {
// Just testing the decryption here. // Just testing the decryption here.
err := wall.Accounts[i].Decrypt(*pass, wall.Scrypt) err := wall.Accounts[i].Decrypt(*pass, wall.Scrypt)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
} }
} }
@ -787,7 +805,7 @@ func dumpKeys(ctx *cli.Context) error {
} }
wall, _, err := readWallet(ctx) wall, _, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
accounts := wall.Accounts accounts := wall.Accounts
@ -796,7 +814,7 @@ func dumpKeys(ctx *cli.Context) error {
if addrFlag.IsSet { if addrFlag.IsSet {
acc := wall.GetAccount(addrFlag.Uint160()) acc := wall.GetAccount(addrFlag.Uint160())
if acc == nil { if acc == nil {
return cli.NewExitError("account is missing", 1) return cli.Exit("account is missing", 1)
} }
accounts = []*wallet.Account{acc} accounts = []*wallet.Account{acc}
} }
@ -826,7 +844,7 @@ func dumpKeys(ctx *cli.Context) error {
continue continue
} }
if addrFlag.IsSet { if addrFlag.IsSet {
return cli.NewExitError(fmt.Errorf("unknown script type for address %s", address.Uint160ToString(addrFlag.Uint160())), 1) return cli.Exit(fmt.Errorf("unknown script type for address %s", address.Uint160ToString(addrFlag.Uint160())), 1)
} }
} }
return nil return nil
@ -838,7 +856,7 @@ func stripKeys(ctx *cli.Context) error {
} }
wall, _, err := readWallet(ctx) wall, _, err := readWallet(ctx)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
if !ctx.Bool("force") { if !ctx.Bool("force") {
@ -851,7 +869,7 @@ func stripKeys(ctx *cli.Context) error {
a.EncryptedWIF = "" a.EncryptedWIF = ""
} }
if err := wall.Save(); err != nil { if err := wall.Save(); err != nil {
return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1) return cli.Exit(fmt.Errorf("error while saving wallet: %w", err), 1)
} }
return nil return nil
} }
@ -867,28 +885,28 @@ func createWallet(ctx *cli.Context) error {
return errConflictingWalletFlags return errConflictingWalletFlags
} }
if len(path) == 0 && len(configPath) == 0 { if len(path) == 0 && len(configPath) == 0 {
return cli.NewExitError(errNoPath, 1) return cli.Exit(errNoPath, 1)
} }
var pass *string var pass *string
if len(configPath) != 0 { if len(configPath) != 0 {
cfg, err := options.ReadWalletConfig(configPath) cfg, err := options.ReadWalletConfig(configPath)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
path = cfg.Path path = cfg.Path
pass = &cfg.Password pass = &cfg.Password
} }
wall, err := wallet.NewWallet(path) wall, err := wallet.NewWallet(path)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if err := wall.Save(); err != nil { if err := wall.Save(); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
if ctx.Bool("account") { if ctx.Bool("account") {
if err := createAccount(wall, pass); err != nil { if err := createAccount(wall, pass); err != nil {
return cli.NewExitError(err, 1) return cli.Exit(err, 1)
} }
defer wall.Close() defer wall.Close()
} }
@ -949,14 +967,14 @@ func createAccount(wall *wallet.Wallet, pass *string) error {
func openWallet(ctx *cli.Context, canUseWalletConfig bool) (*wallet.Wallet, *string, error) { func openWallet(ctx *cli.Context, canUseWalletConfig bool) (*wallet.Wallet, *string, error) {
path, pass, err := getWalletPathAndPass(ctx, canUseWalletConfig) path, pass, err := getWalletPathAndPass(ctx, canUseWalletConfig)
if err != nil { if err != nil {
return nil, nil, cli.NewExitError(fmt.Errorf("failed to get wallet path or password: %w", err), 1) return nil, nil, cli.Exit(fmt.Errorf("failed to get wallet path or password: %w", err), 1)
} }
if path == "-" { if path == "-" {
return nil, nil, errNoStdin return nil, nil, errNoStdin
} }
w, err := wallet.NewWalletFromFile(path) w, err := wallet.NewWalletFromFile(path)
if err != nil { if err != nil {
return nil, nil, cli.NewExitError(fmt.Errorf("failed to read wallet: %w", err), 1) return nil, nil, cli.Exit(fmt.Errorf("failed to read wallet: %w", err), 1)
} }
return w, pass, nil return w, pass, nil
} }

View file

@ -40,10 +40,10 @@ func TestWalletAccountRemove(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("missing wallet", func(t *testing.T) { t.Run("missing wallet", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "remove") e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "remove")
}) })
t.Run("missing address", func(t *testing.T) { t.Run("missing address", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath) e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "remove", "--wallet", walletPath)
}) })
t.Run("invalid address", func(t *testing.T) { t.Run("invalid address", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath, e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath,
@ -109,7 +109,7 @@ func TestWalletChangePassword(t *testing.T) {
e.In.WriteString("pass\r") e.In.WriteString("pass\r")
e.In.WriteString("pass1\r") e.In.WriteString("pass1\r")
e.In.WriteString("pass2\r") e.In.WriteString("pass2\r")
e.RunWithError(t, "neo-go", "wallet", "change-password", "--wallet", walletPath) e.RunWithError(t, "neo-go", "wallet", "change-password", "--wallet", walletPath, "--address", addr1)
}) })
t.Run("good, multiaccount", func(t *testing.T) { t.Run("good, multiaccount", func(t *testing.T) {
e.In.WriteString("pass\r") e.In.WriteString("pass\r")
@ -593,7 +593,7 @@ func TestWalletClaimGas(t *testing.T) {
"--address", testcli.TestWalletAccount) "--address", testcli.TestWalletAccount)
}) })
t.Run("missing address", func(t *testing.T) { t.Run("missing address", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "claim", e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "claim",
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
"--wallet", testcli.TestWalletPath) "--wallet", testcli.TestWalletPath)
}) })
@ -605,7 +605,7 @@ func TestWalletClaimGas(t *testing.T) {
}) })
t.Run("missing endpoint", func(t *testing.T) { t.Run("missing endpoint", func(t *testing.T) {
e.In.WriteString("testpass\r") e.In.WriteString("testpass\r")
e.RunWithError(t, "neo-go", "wallet", "claim", e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "wallet", "claim",
"--wallet", testcli.TestWalletPath, "--wallet", testcli.TestWalletPath,
"--address", testcli.TestWalletAccount) "--address", testcli.TestWalletAccount)
}) })
@ -711,19 +711,19 @@ func TestWalletImportDeployed(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("missing wallet", func(t *testing.T) { t.Run("missing wallet", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "import-deployed") e.RunWithErrorCheck(t, `Required flag "contract" not set`, "neo-go", "wallet", "import-deployed", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
}) })
t.Run("missing contract sh", func(t *testing.T) { t.Run("missing contract sh", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "import-deployed", e.RunWithErrorCheck(t, `Required flag "contract" not set`, "neo-go", "wallet", "import-deployed",
"--wallet", walletPath) "--wallet", walletPath, "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
}) })
t.Run("missing WIF", func(t *testing.T) { t.Run("missing WIF", func(t *testing.T) {
e.RunWithError(t, "neo-go", "wallet", "import-deployed", e.RunWithError(t, "neo-go", "wallet", "import-deployed",
"--wallet", walletPath, "--contract", h.StringLE()) "--wallet", walletPath, "--contract", h.StringLE(), "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
}) })
t.Run("missing endpoint", func(t *testing.T) { t.Run("missing endpoint", func(t *testing.T) {
e.In.WriteString("acc\rpass\rpass\r") e.In.WriteString("acc\rpass\rpass\r")
e.RunWithError(t, "neo-go", "wallet", "import-deployed", e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "wallet", "import-deployed",
"--wallet", walletPath, "--contract", h.StringLE(), "--wallet", walletPath, "--contract", h.StringLE(),
"--wif", priv.WIF()) "--wif", priv.WIF())
}) })
@ -992,7 +992,7 @@ func TestOfflineSigning(t *testing.T) {
e.Run(t, "neo-go", "util", "sendtx", e.Run(t, "neo-go", "util", "sendtx",
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
txPath, "--await") "--await", txPath)
e.CheckAwaitableTxPersisted(t) e.CheckAwaitableTxPersisted(t)
}) })
} }
@ -1107,11 +1107,11 @@ func TestWalletConvert(t *testing.T) {
outPath := filepath.Join(tmpDir, "wallet.json") outPath := filepath.Join(tmpDir, "wallet.json")
cmd := []string{"neo-go", "wallet", "convert"} cmd := []string{"neo-go", "wallet", "convert"}
t.Run("missing wallet", func(t *testing.T) { t.Run("missing wallet", func(t *testing.T) {
e.RunWithError(t, cmd...) e.RunWithErrorCheck(t, `Required flag "out" not set`, cmd...)
}) })
cmd = append(cmd, "--wallet", "testdata/testwallet_NEO2.json") cmd = append(cmd, "--wallet", "testdata/testwallet_NEO2.json")
t.Run("missing out path", func(t *testing.T) { t.Run("missing out path", func(t *testing.T) {
e.RunWithError(t, cmd...) e.RunWithErrorCheck(t, `Required flag "out" not set`, cmd...)
}) })
t.Run("invalid out path", func(t *testing.T) { t.Run("invalid out path", func(t *testing.T) {
dir := t.TempDir() dir := t.TempDir()

5
go.mod
View file

@ -25,7 +25,7 @@ require (
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954
github.com/twmb/murmur3 v1.1.8 github.com/twmb/murmur3 v1.1.8
github.com/urfave/cli v1.22.5 github.com/urfave/cli/v2 v2.27.2
go.etcd.io/bbolt v1.3.9 go.etcd.io/bbolt v1.3.9
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
golang.org/x/crypto v0.21.0 golang.org/x/crypto v0.21.0
@ -42,7 +42,7 @@ require (
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/bavard v0.1.13 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/frankban/quicktest v1.14.5 // indirect github.com/frankban/quicktest v1.14.5 // indirect
github.com/fxamacker/cbor/v2 v2.5.0 // indirect github.com/fxamacker/cbor/v2 v2.5.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
@ -60,6 +60,7 @@ require (
github.com/rs/zerolog v1.30.0 // indirect github.com/rs/zerolog v1.30.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/mod v0.16.0 // indirect golang.org/x/mod v0.16.0 // indirect

15
go.sum
View file

@ -1,4 +1,3 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12 h1:npHgfD4Tl2WJS3AJaMUi5ynGDPUBfkg3U3fCzDyXZ+4=
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20221202181307-76fa05c21b12/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -22,9 +21,8 @@ github.com/consensys/gnark v0.9.1/go.mod h1:udWvWGXnfBE7mn7BsNoGAvZDnUhcONBEtNij
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc= github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb h1:f0BMgIjhZy4lSRHCXFbQst85f5agZAjtDMixQqBWNpc=
github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= github.com/consensys/gnark-crypto v0.12.2-0.20231013160410-1f65e75b6dfb/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -131,20 +129,20 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c= github.com/rs/zerolog v1.30.0 h1:SymVODrcRsaRaSInD9yQtKbtWqwsfoPcRff/oRXLj4c=
github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w= github.com/rs/zerolog v1.30.0/go.mod h1:/tk+P47gFdPXq4QYjvCmT5/Gsug2nagsFWBWhAiSi1w=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954 h1:xQdMZ1WLrgkkvOZ/LDQxjVxMLdby7osSh4ZEVa5sIjs=
github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM= github.com/syndtr/goleveldb v1.0.1-0.20210305035536-64b5b1c73954/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg= github.com/twmb/murmur3 v1.1.8 h1:8Yt9taO/WN3l08xErzjeschgZU2QSrwm1kclYq+0aRg=
github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ= github.com/twmb/murmur3 v1.1.8/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI=
go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@ -216,7 +214,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View file

@ -34,7 +34,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate" "github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
"golang.org/x/term" "golang.org/x/term"
@ -273,13 +273,26 @@ func checkExit(t *testing.T, ch <-chan int, code int) {
} }
} }
// RunWithError runs command and checks that is exits with error. // RunWithError runs command and checks that is exits with error and exit code 1.
func (e *Executor) RunWithError(t *testing.T, args ...string) { func (e *Executor) RunWithError(t *testing.T, args ...string) {
ch := setExitFunc() ch := setExitFunc()
require.Error(t, e.run(args...)) require.Error(t, e.run(args...))
checkExit(t, ch, 1) checkExit(t, ch, 1)
} }
// RunWithErrorCheckExit runs command and checks that is exits with error and exit code 1.
func (e *Executor) RunWithErrorCheckExit(t *testing.T, msg string, args ...string) {
ch := setExitFunc()
require.ErrorContains(t, e.run(args...), msg)
checkExit(t, ch, 1)
}
// RunWithErrorCheck runs command and checks that there were errors with the specified message. Exit code is not checked.
func (e *Executor) RunWithErrorCheck(t *testing.T, msg string, args ...string) {
err := e.run(args...)
require.ErrorContains(t, err, msg)
}
// Run runs command and checks that there were no errors. // Run runs command and checks that there were no errors.
func (e *Executor) Run(t *testing.T, args ...string) { func (e *Executor) Run(t *testing.T, args ...string) {
ch := setExitFunc() ch := setExitFunc()

View file

@ -12,7 +12,7 @@ import (
"path/filepath" "path/filepath"
"sort" "sort"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var ledgerContractID = -4 var ledgerContractID = -4

View file

@ -10,7 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/pmezard/go-difflib/difflib" "github.com/pmezard/go-difflib/difflib"
"github.com/urfave/cli" "github.com/urfave/cli/v2"
) )
var errStateMatches = errors.New("state matches") var errStateMatches = errors.New("state matches")
@ -167,9 +167,10 @@ func main() {
ctl.Usage = "compare-states RPC_A RPC_B" ctl.Usage = "compare-states RPC_A RPC_B"
ctl.Action = cliMain ctl.Action = cliMain
ctl.Flags = []cli.Flag{ ctl.Flags = []cli.Flag{
cli.BoolFlag{ &cli.BoolFlag{
Name: "ignore-height, g", Name: "ignore-height",
Usage: "ignore height difference", Aliases: []string{"h"},
Usage: "Ignore height difference",
}, },
} }