diff --git a/cli/candidate_test.go b/cli/candidate_test.go index ac175458b..ebd0f9144 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -22,6 +22,7 @@ func TestRegisterCandidate(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:"+validatorPriv.Address()+":10", "GAS:"+validatorPriv.Address()+":10000") e.checkTxPersisted(t) diff --git a/cli/contract_test.go b/cli/contract_test.go index 36bd0e811..77ecebb84 100644 --- a/cli/contract_test.go +++ b/cli/contract_test.go @@ -175,6 +175,7 @@ func TestContractDeployWithData(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr, "--in", nefName, "--manifest", manifestName, + "--force", "[", "key1", "12", "key2", "take_me_to_church", "]") e.checkTxPersisted(t, "Sent invocation transaction ") @@ -242,6 +243,7 @@ func TestContractManifestGroups(t *testing.T) { e.Run(t, "neo-go", "contract", "deploy", "--rpc-endpoint", "http://"+e.RPC.Addr, "--in", nefName, "--manifest", manifestName, + "--force", "--wallet", validatorWallet, "--address", validatorAddr) } @@ -261,6 +263,7 @@ func deployContract(t *testing.T, e *executor, inPath, configPath, wallet, addre e.Run(t, "neo-go", "contract", "deploy", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet, "--address", address, + "--force", "--in", nefName, "--manifest", manifestName) e.checkTxPersisted(t, "Sent invocation transaction ") line, err := e.Out.ReadString('\n') @@ -297,7 +300,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.In.WriteString("one\r") e.Run(t, "neo-go", "contract", "deploy", - "--rpc-endpoint", "http://"+e.RPC.Addr, + "--rpc-endpoint", "http://"+e.RPC.Addr, "--force", "--wallet", validatorWallet, "--address", validatorAddr, "--in", nefName, "--manifest", manifestName) @@ -371,11 +374,20 @@ func TestComlileAndInvokeFunction(t *testing.T) { }) cmd = append(cmd, "--wallet", validatorWallet, "--address", validatorAddr) - e.In.WriteString("one\r") - e.Run(t, append(cmd, h.StringLE(), "getValue")...) + t.Run("cancelled", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("n\r") + e.RunWithError(t, append(cmd, h.StringLE(), "getValue")...) + }) + t.Run("confirmed", func(t *testing.T) { + e.In.WriteString("one\r") + e.In.WriteString("y\r") + e.Run(t, append(cmd, h.StringLE(), "getValue")...) + }) t.Run("failind method", func(t *testing.T) { e.In.WriteString("one\r") + e.In.WriteString("y\r") e.RunWithError(t, append(cmd, h.StringLE(), "fail")...) e.In.WriteString("one\r") @@ -384,6 +396,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { t.Run("cosigner is deployed contract", func(t *testing.T) { e.In.WriteString("one\r") + e.In.WriteString("y\r") e.Run(t, append(cmd, h.StringLE(), "getValue", "--", validatorAddr, hVerify.StringLE())...) }) @@ -499,6 +512,7 @@ func TestComlileAndInvokeFunction(t *testing.T) { e.Run(t, "neo-go", "contract", "invokefunction", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--address", validatorAddr, + "--force", h.StringLE(), "update", "bytes:"+hex.EncodeToString(rawNef), "bytes:"+hex.EncodeToString(rawManifest), diff --git a/cli/input/input.go b/cli/input/input.go index 22eb4a6c8..c8445766d 100644 --- a/cli/input/input.go +++ b/cli/input/input.go @@ -1,10 +1,13 @@ package input import ( + "errors" + "fmt" "io" "os" "syscall" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "golang.org/x/term" ) @@ -55,3 +58,18 @@ func ReadPassword(prompt string) (string, error) { } return trm.ReadPassword(prompt) } + +// ConfirmTx asks for a confirmation to send tx. +func ConfirmTx(w io.Writer, tx *transaction.Transaction) error { + fmt.Fprintf(w, "Network fee: %d\n", tx.NetworkFee) + fmt.Fprintf(w, "System fee: %d\n", tx.SystemFee) + fmt.Fprintf(w, "Total fee: %d\n", tx.NetworkFee+tx.SystemFee) + ln, err := ReadLine("Relay transaction (y|N)> ") + if err != nil { + return err + } + if 0 < len(ln) && ln[0] == 'y' || ln[0] == 'Y' { + return nil + } + return errors.New("cancelled") +} diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 95d1da0d8..e189f1518 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -51,6 +51,7 @@ func TestSignMultisigTx(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:"+multisigAddr+":4", "GAS:"+multisigAddr+":1") e.checkTxPersisted(t) diff --git a/cli/nep11_test.go b/cli/nep11_test.go index 69a83e1e3..576d7184c 100644 --- a/cli/nep11_test.go +++ b/cli/nep11_test.go @@ -107,6 +107,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { "--to", nftOwnerAddr, "--token", "GAS", "--amount", "10000", + "--force", "--from", validatorAddr) e.checkTxPersisted(t) @@ -122,6 +123,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { "--to", h.StringLE(), "--token", "GAS", "--amount", "10", + "--force", "--from", nftOwnerAddr) txMint, _ := e.checkTxPersisted(t) @@ -259,6 +261,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { "--wallet", wall, "--to", validatorAddr, "--from", nftOwnerAddr, + "--force", } // transfer: unimported token with symbol id specified @@ -290,6 +293,7 @@ func TestNEP11_OwnerOf_BalanceOf_Transfer(t *testing.T) { "--from", nftOwnerAddr, "--token", h.StringLE(), "--id", string(tokenID1), + "--force", "string:some_data", } e.In.WriteString(nftOwnerPass + "\r") diff --git a/cli/nep17_test.go b/cli/nep17_test.go index 68ad55b7f..d8b2021ee 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -122,8 +122,23 @@ func TestNEP17Transfer(t *testing.T) { e.In.Reset() }) + t.Run("no confirmation", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, args...) + e.In.Reset() + }) + t.Run("cancel after prompt", func(t *testing.T) { + e.In.WriteString("one\r") + e.RunWithError(t, args...) + e.In.Reset() + }) + e.In.WriteString("one\r") + e.In.WriteString("Y\r") e.Run(t, args...) + e.checkNextLine(t, `^Network fee:\s*(\d|\.)+`) + e.checkNextLine(t, `^System fee:\s*(\d|\.)+`) + e.checkNextLine(t, `^Total fee:\s*(\d|\.)+`) e.checkTxPersisted(t) sh, err := address.StringToUint160(w.Accounts[0].Address) @@ -131,6 +146,17 @@ func TestNEP17Transfer(t *testing.T) { b, _ := e.Chain.GetGoverningTokenBalance(sh) require.Equal(t, big.NewInt(1), b) + t.Run("with force", func(t *testing.T) { + e.In.WriteString("one\r") + e.Run(t, append(args, "--force")...) + e.checkTxPersisted(t) + + sh, err := address.StringToUint160(w.Accounts[0].Address) + require.NoError(t, err) + b, _ := e.Chain.GetGoverningTokenBalance(sh) + require.Equal(t, big.NewInt(2), b) + }) + hVerify := deployVerifyContract(t, e) const validatorDefault = "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn" @@ -140,11 +166,13 @@ func TestNEP17Transfer(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:"+validatorDefault+":42", "GAS:"+validatorDefault+":7") e.checkTxPersisted(t) args := args[:len(args)-2] // cut '--from' argument + args = append(args, "--force") e.In.WriteString("one\r") e.Run(t, args...) e.checkTxPersisted(t) @@ -152,7 +180,7 @@ func TestNEP17Transfer(t *testing.T) { sh, err := address.StringToUint160(w.Accounts[0].Address) require.NoError(t, err) b, _ := e.Chain.GetGoverningTokenBalance(sh) - require.Equal(t, big.NewInt(2), b) + require.Equal(t, big.NewInt(3), b) sh, err = address.StringToUint160(validatorDefault) require.NoError(t, err) @@ -166,6 +194,7 @@ func TestNEP17Transfer(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:"+validatorDefault+":42", "GAS:"+validatorDefault+":7", "--", validatorAddr+":Global") @@ -181,6 +210,7 @@ func TestNEP17Transfer(t *testing.T) { "--token", "GAS", "--amount", "1", "--from", validatorAddr, + "--force", "[", validatorAddr, strconv.Itoa(int(validTil)), "]"} t.Run("with data", func(t *testing.T) { @@ -218,6 +248,7 @@ func TestNEP17MultiTransfer(t *testing.T) { "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:" + privs[0].Address() + ":42", "GAS:" + privs[1].Address() + ":7", neoContractHash.StringLE() + ":" + privs[2].Address() + ":13", diff --git a/cli/query_test.go b/cli/query_test.go index 5e4c9a516..f0fc761fb 100644 --- a/cli/query_test.go +++ b/cli/query_test.go @@ -34,6 +34,7 @@ func TestQueryTx(t *testing.T) { "--to", w.Accounts[0].Address, "--token", "NEO", "--from", validatorAddr, + "--force", } e.In.WriteString("one\r") diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index dc7ac6512..808e6b9c6 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -660,7 +660,17 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, if len(resp.Script) == 0 { return sender, cli.NewExitError(errors.New("no script returned from the RPC node"), 1) } - txHash, err := c.SignAndPushInvocationTx(resp.Script, acc, resp.GasConsumed+int64(sysgas), gas, cosignersAccounts) + tx, err := c.CreateTxFromScript(resp.Script, acc, resp.GasConsumed+int64(sysgas), int64(gas), cosignersAccounts) + if err != nil { + return sender, cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) + } + if !ctx.Bool("force") { + err := input.ConfirmTx(ctx.App.Writer, tx) + if err != nil { + return sender, cli.NewExitError(err, 1) + } + } + txHash, err := c.SignAndPushTx(tx, acc, cosignersAccounts) if err != nil { return sender, cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) } diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index 9f3d64234..e0393484e 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -6,6 +6,7 @@ import ( "math/big" "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -262,6 +263,12 @@ func signAndSendNEP11Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Ac return cli.NewExitError(err, 1) } } else { + if !ctx.Bool("force") { + err := input.ConfirmTx(ctx.App.Writer, tx) + if err != nil { + return cli.NewExitError(err, 1) + } + } _, err := c.SignAndPushTx(tx, acc, cosigners) if err != nil { return cli.NewExitError(err, 1) diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index cbf60b974..270a60c77 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -55,6 +56,7 @@ var ( tokenFlag, gasFlag, sysGasFlag, + forceFlag, cli.StringFlag{ Name: "amount", Usage: "Amount of asset to send", @@ -66,6 +68,7 @@ var ( fromAddrFlag, gasFlag, sysGasFlag, + forceFlag, }, options.RPC...) ) @@ -580,6 +583,12 @@ func signAndSendNEP17Transfer(ctx *cli.Context, c *client.Client, acc *wallet.Ac return cli.NewExitError(err, 1) } } else { + if !ctx.Bool("force") { + err := input.ConfirmTx(ctx.App.Writer, tx) + if err != nil { + return cli.NewExitError(err, 1) + } + } _, err := c.SignAndPushTx(tx, acc, cosigners) if err != nil { return cli.NewExitError(err, 1) diff --git a/cli/wallet_test.go b/cli/wallet_test.go index c5855f341..21067f7de 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -222,6 +222,7 @@ func TestClaimGas(t *testing.T) { "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:" + testWalletAccount + ":1000", "GAS:" + testWalletAccount + ":1000", // for tx send } @@ -293,6 +294,7 @@ func TestImportDeployed(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, + "--force", "NEO:"+contractAddr+":10", "GAS:"+contractAddr+":10") e.checkTxPersisted(t) @@ -304,6 +306,7 @@ func TestImportDeployed(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "transfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, "--from", contractAddr, + "--force", "--to", privTo.Address(), "--token", "NEO", "--amount", "1") e.checkTxPersisted(t)