From 152cebe4e2e7457807053110b3416a0aca5db45b Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 31 Jan 2022 16:26:23 +0300 Subject: [PATCH] cli: add tests for wallet-related commands Fixes: * Return proper exit code on error. * Improve error handling. --- cli/multisig_test.go | 60 +++++++++- cli/wallet/wallet.go | 11 +- cli/wallet_test.go | 262 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 309 insertions(+), 24 deletions(-) diff --git a/cli/multisig_test.go b/cli/multisig_test.go index f673b0af5..654f131ce 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -14,6 +14,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/stretchr/testify/require" ) @@ -64,6 +65,51 @@ func TestSignMultisigTx(t *testing.T) { priv, err := keys.NewPrivateKey() require.NoError(t, err) + t.Run("bad cases", func(t *testing.T) { + txPath := filepath.Join(tmpDir, "multisigtx.json") + t.Cleanup(func() { + os.Remove(txPath) + }) + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "nep17", "transfer", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet1Path, "--from", multisigAddr, + "--to", priv.Address(), "--token", "NEO", "--amount", "1", + "--out", txPath) + + // missing wallet + e.RunWithError(t, "neo-go", "wallet", "sign") + + // missing in + e.RunWithError(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path) + + // missing address + e.RunWithError(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path, + "--in", txPath) + + // invalid address + e.RunWithError(t, "neo-go", "wallet", "sign", + "--wallet", wallet2Path, "--address", util.Uint160{}.StringLE(), + "--in", txPath) + + // invalid out + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath, "--out", t.TempDir()) + + // invalid RPC endpoint + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://not-an-address", + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath) + }) + + // Create transaction and save it for further multisigning. txPath := filepath.Join(tmpDir, "multisigtx.json") t.Cleanup(func() { os.Remove(txPath) @@ -90,12 +136,6 @@ func TestSignMultisigTx(t *testing.T) { }) } - // missing address - e.RunWithError(t, "neo-go", "wallet", "sign", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", wallet2Path, - "--in", txPath, "--out", txPath) - t.Run("test invoke", func(t *testing.T) { t.Run("missing file", func(t *testing.T) { e.RunWithError(t, "neo-go", "util", "txdump") @@ -124,6 +164,14 @@ func TestSignMultisigTx(t *testing.T) { "--in", txPath, "--out", txPath) e.checkTxPersisted(t) + t.Run("double-sign", func(t *testing.T) { + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "sign", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", wallet2Path, "--address", multisigAddr, + "--in", txPath, "--out", txPath) + }) + b, _ := e.Chain.GetGoverningTokenBalance(priv.GetScriptHash()) require.Equal(t, big.NewInt(1), b) b, _ = e.Chain.GetGoverningTokenBalance(multisigHash) diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 0d62f1840..391326de1 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -356,7 +356,11 @@ func convertWallet(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - newWallet, err := wallet.NewWallet(ctx.String("out")) + out := ctx.String("out") + if len(out) == 0 { + return cli.NewExitError("missing out path", 1) + } + newWallet, err := wallet.NewWallet(out) if err != nil { return cli.NewExitError(err, 1) } @@ -375,7 +379,7 @@ func convertWallet(ctx *cli.Context) error { newWallet.AddAccount(newAcc) } if err := newWallet.Save(); err != nil { - return cli.NewExitError(err, -1) + return cli.NewExitError(err, 1) } return nil } @@ -605,7 +609,8 @@ func removeAccount(ctx *cli.Context) error { if err := wall.RemoveAccount(acc.Address); err != nil { return cli.NewExitError(fmt.Errorf("error on remove: %w", err), 1) - } else 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 nil diff --git a/cli/wallet_test.go b/cli/wallet_test.go index dc36cb5f0..504ffe3a9 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -3,7 +3,6 @@ package main import ( "encoding/hex" "encoding/json" - "io/ioutil" "math/big" "path/filepath" "strings" @@ -14,6 +13,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) @@ -36,13 +36,34 @@ func TestWalletAccountRemove(t *testing.T) { w, err := wallet.NewWalletFromFile(walletPath) require.NoError(t, err) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "remove") + }) + t.Run("missing address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath) + }) + t.Run("invalid address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath, + "--address", util.Uint160{}.StringLE()) + }) + addr := w.Accounts[0].Address + t.Run("askForConsent > no", func(t *testing.T) { + e.In.WriteString("no") + e.Run(t, "neo-go", "wallet", "remove", "--wallet", walletPath, + "--address", addr) + actual, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, w, actual) + }) + e.Run(t, "neo-go", "wallet", "remove", "--wallet", walletPath, "--address", addr, "--force") - rawWallet, err := ioutil.ReadFile(walletPath) + actual, err := wallet.NewWalletFromFile(walletPath) require.NoError(t, err) - require.NoError(t, json.Unmarshal(rawWallet, new(wallet.Wallet))) + require.Equal(t, 1, len(actual.Accounts)) + require.Equal(t, w.Accounts[1], actual.Accounts[0]) } func TestWalletChangePassword(t *testing.T) { @@ -65,6 +86,12 @@ func TestWalletChangePassword(t *testing.T) { addr1 := w.Accounts[0].Address addr2 := w.Accounts[1].Address + t.Run("missing wallet path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "change-password") + }) + t.Run("EOF reading old password", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "change-password", "--wallet", walletPath) + }) t.Run("bad old password", func(t *testing.T) { e.In.WriteString("ssap\r") e.In.WriteString("aaa\r") // Pretend for the password to be fine. @@ -112,9 +139,46 @@ func TestWalletChangePassword(t *testing.T) { } func TestWalletInit(t *testing.T) { - tmpDir := t.TempDir() e := newExecutor(t, false) + t.Run("missing path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "init") + }) + t.Run("invalid path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", t.TempDir()) + }) + t.Run("good: no account", func(t *testing.T) { + walletPath := filepath.Join(t.TempDir(), "wallet.json") + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, 0, len(w.Accounts)) + }) + t.Run("with account", func(t *testing.T) { + walletPath := filepath.Join(t.TempDir(), "wallet.json") + t.Run("missing acc name", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + }) + t.Run("missing pass", func(t *testing.T) { + e.In.WriteString("acc\r") + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + }) + t.Run("missing second pass", func(t *testing.T) { + e.In.WriteString("acc\r") + e.In.WriteString("pass\r") + e.RunWithError(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + }) + e.In.WriteString("acc\r") + e.In.WriteString("pass\r") + e.In.WriteString("pass\r") + e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath, "--account") + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + require.Equal(t, 1, len(w.Accounts)) + require.Equal(t, "acc", w.Accounts[0].Label) + }) + + tmpDir := t.TempDir() walletPath := filepath.Join(tmpDir, "wallet.json") e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) @@ -144,6 +208,12 @@ func TestWalletInit(t *testing.T) { t.Run("stdin", func(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "create", "--wallet", "-") }) + t.Run("passwords mismatch", func(t *testing.T) { + e.In.WriteString("testname\r") + e.In.WriteString("testpass\r") + e.In.WriteString("badpass\r") + e.RunWithError(t, "neo-go", "wallet", "create", "--wallet", walletPath) + }) e.In.WriteString("testname\r") e.In.WriteString("testpass\r") e.In.WriteString("testpass\r") @@ -171,6 +241,9 @@ func TestWalletInit(t *testing.T) { t.Run("Import", func(t *testing.T) { t.Run("WIF", func(t *testing.T) { + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import") + }) priv, err := keys.NewPrivateKey() require.NoError(t, err) e.In.WriteString("test_account\r") @@ -194,6 +267,33 @@ func TestWalletInit(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "import", "--wallet", walletPath, "--wif", priv.WIF()) }) + + t.Run("contract", func(t *testing.T) { + priv, err = keys.NewPrivateKey() + require.NoError(t, err) + + t.Run("invalid script", func(t *testing.T) { + e.In.WriteString("test_account_3\r") + e.In.WriteString("qwerty\r") + e.In.WriteString("qwerty\r") + e.RunWithError(t, "neo-go", "wallet", "import", + "--wallet", walletPath, "--wif", priv.WIF(), "--contract", "not-a-hex") + }) + + e.In.WriteString("test_account_3\r") + e.In.WriteString("qwerty\r") + e.In.WriteString("qwerty\r") + e.Run(t, "neo-go", "wallet", "import", + "--wallet", walletPath, "--wif", priv.WIF(), "--contract", "0a0b0c") + + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + t.Cleanup(w.Close) + acc := w.GetAccount(priv.GetScriptHash()) + require.NotNil(t, acc) + require.Equal(t, "test_account_3", acc.Label) + require.NoError(t, acc.Decrypt("qwerty", w.Scrypt)) + }) }) t.Run("EncryptedWIF", func(t *testing.T) { acc, err := wallet.NewAccount() @@ -218,12 +318,31 @@ func TestWalletInit(t *testing.T) { require.NoError(t, actual.Decrypt("somepass", w.Scrypt)) }) t.Run("Multisig", func(t *testing.T) { + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-multisig") + }) + t.Run("insufficient pubs", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-multisig", + "--wallet", walletPath, + "--min", "2") + }) privs, pubs := generateKeys(t, 4) - cmd := []string{"neo-go", "wallet", "import-multisig", "--wallet", walletPath, - "--wif", privs[0].WIF(), "--min", "2"} + t.Run("invalid pub encoding", func(t *testing.T) { + e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[2].Bytes()), + "not-a-pub")...) + }) + t.Run("missing WIF", func(t *testing.T) { + e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[0].Bytes()), + hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[2].Bytes()), + hex.EncodeToString(pubs[3].Bytes()))...) + }) + cmd = append(cmd, "--wif", privs[0].WIF()) t.Run("InvalidPublicKeys", func(t *testing.T) { e.In.WriteString("multiacc\r") e.In.WriteString("multipass\r") @@ -253,6 +372,16 @@ func TestWalletInit(t *testing.T) { require.NotNil(t, actual) require.NoError(t, actual.Decrypt("multipass", w.Scrypt)) require.Equal(t, script, actual.Contract.Script) + + t.Run("double-import", func(t *testing.T) { + e.In.WriteString("multiacc\r") + e.In.WriteString("multipass\r") + e.In.WriteString("multipass\r") + e.RunWithError(t, append(cmd, hex.EncodeToString(pubs[0].Bytes()), + hex.EncodeToString(pubs[1].Bytes()), + hex.EncodeToString(pubs[2].Bytes()), + hex.EncodeToString(pubs[3].Bytes()))...) + }) }) }) } @@ -260,6 +389,13 @@ func TestWalletInit(t *testing.T) { func TestWalletExport(t *testing.T) { e := newExecutor(t, false) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "export") + }) + t.Run("invalid address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "export", + "--wallet", validatorWallet, "not-an-address") + }) t.Run("Encrypted", func(t *testing.T) { e.Run(t, "neo-go", "wallet", "export", "--wallet", validatorWallet, validatorAddr) @@ -274,6 +410,15 @@ func TestWalletExport(t *testing.T) { e.RunWithError(t, "neo-go", "wallet", "export", "--wallet", validatorWallet, "--decrypt") }) + t.Run("EOF reading password", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "export", + "--wallet", validatorWallet, "--decrypt", validatorAddr) + }) + t.Run("invalid password", func(t *testing.T) { + e.In.WriteString("invalid_pass\r") + e.RunWithError(t, "neo-go", "wallet", "export", + "--wallet", validatorWallet, "--decrypt", validatorAddr) + }) e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "export", "--wallet", validatorWallet, "--decrypt", validatorAddr) @@ -283,9 +428,39 @@ func TestWalletExport(t *testing.T) { }) } -func TestClaimGas(t *testing.T) { +func TestWalletClaimGas(t *testing.T) { e := newExecutor(t, true) + t.Run("missing wallet path", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--address", testWalletAccount) + }) + t.Run("missing address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", testWalletPath) + }) + t.Run("invalid address", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", testWalletPath, + "--address", util.Uint160{}.StringLE()) + }) + t.Run("missing endpoint", func(t *testing.T) { + e.In.WriteString("testpass\r") + e.RunWithError(t, "neo-go", "wallet", "claim", + "--wallet", testWalletPath, + "--address", testWalletAccount) + }) + t.Run("insufficient funds", func(t *testing.T) { + e.In.WriteString("testpass\r") + e.RunWithError(t, "neo-go", "wallet", "claim", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", testWalletPath, + "--address", testWalletAccount) + }) + args := []string{ "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://" + e.RPC.Addr, @@ -326,7 +501,7 @@ func TestClaimGas(t *testing.T) { } } -func TestImportDeployed(t *testing.T) { +func TestWalletImportDeployed(t *testing.T) { tmpDir := t.TempDir() e := newExecutor(t, true) h := deployVerifyContract(t, e) @@ -337,15 +512,43 @@ func TestImportDeployed(t *testing.T) { priv, err := keys.NewPrivateKey() require.NoError(t, err) - // missing contract sh - e.RunWithError(t, "neo-go", "wallet", "import-deployed", - "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", walletPath, "--wif", priv.WIF()) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-deployed") + }) + t.Run("missing contract sh", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--wallet", walletPath) + }) + t.Run("missing WIF", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--wallet", walletPath, "--contract", h.StringLE()) + }) + t.Run("missing endpoint", func(t *testing.T) { + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--wallet", walletPath, "--contract", h.StringLE(), + "--wif", priv.WIF()) + }) + t.Run("unknown contract", func(t *testing.T) { + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", walletPath, "--contract", util.Uint160{}.StringLE(), + "--wif", priv.WIF()) + }) + t.Run("no `verify` method", func(t *testing.T) { + badH := deployNNSContract(t, e) // wrong contract with no `verify` method + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", walletPath, "--contract", badH.StringLE(), + "--wif", priv.WIF()) + }) e.In.WriteString("acc\rpass\rpass\r") e.Run(t, "neo-go", "wallet", "import-deployed", "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", walletPath, "--wif", priv.WIF(), + "--wallet", walletPath, "--wif", priv.WIF(), "--name", "my_acc", "--contract", h.StringLE()) w, err := wallet.NewWalletFromFile(walletPath) @@ -358,6 +561,14 @@ func TestImportDeployed(t *testing.T) { require.Equal(t, address.Uint160ToString(h), contractAddr) require.True(t, w.Accounts[0].Contract.Deployed) + t.Run("re-importing", func(t *testing.T) { + e.In.WriteString("acc\rpass\rpass\r") + e.RunWithError(t, "neo-go", "wallet", "import-deployed", + "--rpc-endpoint", "http://"+e.RPC.Addr, + "--wallet", walletPath, "--wif", priv.WIF(), "--name", "my_acc", + "--contract", h.StringLE()) + }) + t.Run("Sign", func(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", @@ -389,6 +600,9 @@ func TestImportDeployed(t *testing.T) { func TestWalletDump(t *testing.T) { e := newExecutor(t, false) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "dump") + }) cmd := []string{"neo-go", "wallet", "dump", "--wallet", testWalletPath} e.Run(t, cmd...) rawStr := strings.TrimSpace(e.Out.String()) @@ -399,6 +613,9 @@ func TestWalletDump(t *testing.T) { t.Run("with decrypt", func(t *testing.T) { cmd = append(cmd, "--decrypt") + t.Run("EOF reading password", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) t.Run("invalid password", func(t *testing.T) { e.In.WriteString("invalidpass\r") e.RunWithError(t, cmd...) @@ -414,8 +631,11 @@ func TestWalletDump(t *testing.T) { }) } -func TestDumpKeys(t *testing.T) { +func TestWalletDumpKeys(t *testing.T) { e := newExecutor(t, false) + t.Run("missing wallet", func(t *testing.T) { + e.RunWithError(t, "neo-go", "wallet", "dump-keys") + }) cmd := []string{"neo-go", "wallet", "dump-keys", "--wallet", validatorWallet} pubRegex := "^0[23][a-hA-H0-9]{64}$" t.Run("all", func(t *testing.T) { @@ -432,6 +652,10 @@ func TestDumpKeys(t *testing.T) { e.checkNextLine(t, pubRegex) e.checkEOF(t) }) + t.Run("unknown address", func(t *testing.T) { + cmd := append(cmd, "--address", util.Uint160{}.StringLE()) + e.RunWithError(t, cmd...) + }) t.Run("simple signature", func(t *testing.T) { cmd := append(cmd, "--address", "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn") e.Run(t, cmd...) @@ -467,8 +691,16 @@ func TestWalletConvert(t *testing.T) { t.Run("missing wallet", func(t *testing.T) { e.RunWithError(t, cmd...) }) + cmd = append(cmd, "--wallet", "testdata/wallets/testwallet_NEO2.json") + t.Run("missing out path", func(t *testing.T) { + e.RunWithError(t, cmd...) + }) + t.Run("invalid out path", func(t *testing.T) { + dir := t.TempDir() + e.RunWithError(t, append(cmd, "--out", dir)...) + }) - cmd = append(cmd, "--wallet", "testdata/wallets/testwallet_NEO2.json", "--out", outPath) + cmd = append(cmd, "--out", outPath) t.Run("invalid password", func(t *testing.T) { // missing password e.RunWithError(t, cmd...)