diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index ef2159dd3..15a5e85cc 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -8,19 +8,17 @@ import ( "os" "path/filepath" "strings" - "time" "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" - "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/cli/txctx" cliwallet "github.com/nspcc-dev/neo-go/cli/wallet" "github.com/nspcc-dev/neo-go/pkg/compiler" "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/encoding/address" - "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker" @@ -62,22 +60,6 @@ var ( Name: addressFlagName, Usage: "address to use as transaction signee (and gas source)", } - gasFlag = flags.Fixed8Flag{ - Name: "gas, g", - Usage: "network fee to add to the transaction (prioritizing it)", - } - sysGasFlag = flags.Fixed8Flag{ - Name: "sysgas, e", - Usage: "system fee to add to transaction (compensating for execution)", - } - outFlag = cli.StringFlag{ - Name: "out", - Usage: "file to put JSON transaction to", - } - forceFlag = cli.BoolFlag{ - Name: "force", - Usage: "force-push the transaction in case of bad VM state after test script invocation", - } ) // ModVersion contains `pkg/interop` module version @@ -120,10 +102,10 @@ func NewCommands() []cli.Command { walletFlag, walletConfigFlag, addressFlag, - gasFlag, - sysGasFlag, - outFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.OutFlag, + txctx.ForceFlag, } invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...) deployFlags := append(invokeFunctionFlags, []cli.Flag{ @@ -668,7 +650,6 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error { func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []interface{}, cosigners []transaction.Signer) error { var ( err error - gas, sysgas fixedn.Fixed8 signersAccounts []actor.SignerAccount resp *result.Invoke signAndPush = acc != nil @@ -676,8 +657,6 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, act *actor.Actor ) if signAndPush { - gas = flags.Fixed8FromContext(ctx, "gas") - sysgas = flags.Fixed8FromContext(ctx, "sysgas") signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None) if err != nil { return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1) @@ -731,44 +710,16 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, } fmt.Fprintln(ctx.App.Writer, string(b)) - } else { - if len(resp.Script) == 0 { - return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) - } - ver := act.GetVersion() - tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed+int64(sysgas), nil) - if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) - } - tx.NetworkFee += int64(gas) - if out != "" { - // Make a long-lived transaction, it's to be signed manually. - tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2 - m := act.GetNetwork() - if err := paramcontext.InitAndSave(m, tx, acc, out); err != nil { - return cli.NewExitError(err, 1) - } - fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) - } else { - if !ctx.Bool("force") { - promptTime := time.Now() - err := input.ConfirmTx(ctx.App.Writer, tx) - if err != nil { - return cli.NewExitError(err, 1) - } - waitTime := time.Since(promptTime) - // Compensate for confirmation waiting. - tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1 - } - txHash, _, err := act.SignAndSend(tx) - if err != nil { - return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1) - } - fmt.Fprintf(ctx.App.Writer, "Sent invocation transaction %s\n", txHash.StringLE()) - } + return nil } - - return nil + if len(resp.Script) == 0 { + return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) + } + tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed, nil) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1) + } + return txctx.SignAndSend(ctx, act, acc, tx) } func testInvokeScript(ctx *cli.Context) error { diff --git a/cli/txctx/tx.go b/cli/txctx/tx.go new file mode 100644 index 000000000..64a6b0f67 --- /dev/null +++ b/cli/txctx/tx.go @@ -0,0 +1,79 @@ +/* +Package txctx contains helper functions that deal with transactions in CLI context. +*/ +package txctx + +import ( + "fmt" + "time" + + "github.com/nspcc-dev/neo-go/cli/flags" + "github.com/nspcc-dev/neo-go/cli/input" + "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" + "github.com/nspcc-dev/neo-go/pkg/wallet" + "github.com/urfave/cli" +) + +var ( + // GasFlag is a flag used for the additional network fee. + GasFlag = flags.Fixed8Flag{ + Name: "gas, g", + Usage: "network fee to add to the transaction (prioritizing it)", + } + // SysGasFlag is a flag used for the additional system fee. + SysGasFlag = flags.Fixed8Flag{ + Name: "sysgas, e", + Usage: "system fee to add to the transaction (compensating for execution)", + } + // OutFlag is a flag used for file output. + OutFlag = cli.StringFlag{ + Name: "out", + Usage: "file (JSON) to put signature context with a transaction to", + } + // ForceFlag is a flag used to force transaction send. + ForceFlag = cli.BoolFlag{ + Name: "force", + Usage: "Do not ask for a confirmation (and ignore errors)", + } +) + +// SignAndSend adds network and system fees to the provided transaction and +// either sends it to the network (with a confirmation or --force flag) or saves +// it into a file (given in the --out flag). +func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *transaction.Transaction) error { + var ( + err error + gas = flags.Fixed8FromContext(ctx, "gas") + sysgas = flags.Fixed8FromContext(ctx, "sysgas") + ver = act.GetVersion() + ) + + tx.SystemFee += int64(sysgas) + tx.NetworkFee += int64(gas) + + if outFile := ctx.String("out"); outFile != "" { + // Make a long-lived transaction, it's to be signed manually. + tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2 + err = paramcontext.InitAndSave(ver.Protocol.Network, tx, acc, outFile) + } else { + if !ctx.Bool("force") { + promptTime := time.Now() + err := input.ConfirmTx(ctx.App.Writer, tx) + if err != nil { + return cli.NewExitError(err, 1) + } + waitTime := time.Since(promptTime) + // Compensate for confirmation waiting. + tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1 + } + _, _, err = act.SignAndSend(tx) + } + if err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) + return nil +} diff --git a/cli/wallet/nep11.go b/cli/wallet/nep11.go index 5fb8c950e..8c5b9caf6 100644 --- a/cli/wallet/nep11.go +++ b/cli/wallet/nep11.go @@ -9,6 +9,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/options" + "github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" @@ -94,7 +95,7 @@ func newNEP11Commands() []cli.Command { walletPathFlag, walletConfigFlag, tokenFlag, - forceFlag, + txctx.ForceFlag, }, }, { diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index 6a778cfa3..7a7ad5a4e 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -6,13 +6,11 @@ import ( "fmt" "math/big" "strings" - "time" "github.com/nspcc-dev/neo-go/cli/cmdargs" "github.com/nspcc-dev/neo-go/cli/flags" - "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" - "github.com/nspcc-dev/neo-go/cli/paramcontext" + "github.com/nspcc-dev/neo-go/cli/txctx" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -37,14 +35,6 @@ var ( Name: "token", Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))", } - gasFlag = flags.Fixed8Flag{ - Name: "gas, g", - Usage: "network fee to add to the transaction (prioritizing it)", - } - sysGasFlag = flags.Fixed8Flag{ - Name: "sysgas, e", - Usage: "system fee to add to transaction (compensating for execution)", - } baseBalanceFlags = []cli.Flag{ walletPathFlag, walletConfigFlag, @@ -65,13 +55,13 @@ var ( baseTransferFlags = []cli.Flag{ walletPathFlag, walletConfigFlag, - outFlag, + txctx.OutFlag, fromAddrFlag, toAddrFlag, tokenFlag, - gasFlag, - sysGasFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.ForceFlag, cli.StringFlag{ Name: "amount", Usage: "Amount of asset to send", @@ -80,11 +70,11 @@ var ( multiTransferFlags = append([]cli.Flag{ walletPathFlag, walletConfigFlag, - outFlag, + txctx.OutFlag, fromAddrFlag, - gasFlag, - sysGasFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.ForceFlag, }, options.RPC...) ) @@ -143,7 +133,7 @@ func newNEP17Commands() []cli.Command { walletPathFlag, walletConfigFlag, tokenFlag, - forceFlag, + txctx.ForceFlag, }, }, { @@ -599,7 +589,7 @@ func multiTransferNEP17(ctx *cli.Context) error { if err != nil { return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) } - return signAndSendSomeTransaction(ctx, act, acc, tx) + return txctx.SignAndSend(ctx, act, acc, tx) } func transferNEP17(ctx *cli.Context) error { @@ -697,7 +687,7 @@ func transferNEP(ctx *cli.Context, standard string) error { return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1) } - return signAndSendSomeTransaction(ctx, act, acc, tx) + return txctx.SignAndSend(ctx, act, acc, tx) } func makeMultiTransferNEP17(act *actor.Actor, recipients []rpcclient.TransferTarget) (*transaction.Transaction, error) { @@ -713,42 +703,6 @@ func makeMultiTransferNEP17(act *actor.Actor, recipients []rpcclient.TransferTar return act.MakeUnsignedRun(script, nil) } -func signAndSendSomeTransaction(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *transaction.Transaction) error { - var ( - err error - gas = flags.Fixed8FromContext(ctx, "gas") - sysgas = flags.Fixed8FromContext(ctx, "sysgas") - ) - - tx.SystemFee += int64(sysgas) - tx.NetworkFee += int64(gas) - - ver := act.GetVersion() - if outFile := ctx.String("out"); outFile != "" { - // Make a long-lived transaction, it's to be signed manually. - tx.ValidUntilBlock += (ver.Protocol.MaxValidUntilBlockIncrement - uint32(ver.Protocol.ValidatorsCount)) - 2 - err = paramcontext.InitAndSave(ver.Protocol.Network, tx, acc, outFile) - } else { - if !ctx.Bool("force") { - promptTime := time.Now() - err := input.ConfirmTx(ctx.App.Writer, tx) - if err != nil { - return cli.NewExitError(err, 1) - } - waitTime := time.Since(promptTime) - // Compensate for confirmation waiting. - tx.ValidUntilBlock += uint32((waitTime.Milliseconds() / int64(ver.Protocol.MillisecondsPerBlock))) + 1 - } - _, _, err = act.SignAndSend(tx) - } - if err != nil { - return cli.NewExitError(err, 1) - } - - fmt.Fprintln(ctx.App.Writer, tx.Hash().StringLE()) - return nil -} - func getDefaultAddress(fromFlag *flags.Address, w *wallet.Wallet) (util.Uint160, error) { if fromFlag.IsSet { return fromFlag.Uint160(), nil diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index e4a9e17ad..1194fa371 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -7,6 +7,7 @@ import ( "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/txctx" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -27,10 +28,10 @@ func newValidatorCommands() []cli.Command { Flags: append([]cli.Flag{ walletPathFlag, walletConfigFlag, - gasFlag, - sysGasFlag, - outFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.OutFlag, + txctx.ForceFlag, flags.AddressFlag{ Name: "address, a", Usage: "Address to register", @@ -45,10 +46,10 @@ func newValidatorCommands() []cli.Command { Flags: append([]cli.Flag{ walletPathFlag, walletConfigFlag, - gasFlag, - sysGasFlag, - outFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.OutFlag, + txctx.ForceFlag, flags.AddressFlag{ Name: "address, a", Usage: "Address to unregister", @@ -66,10 +67,10 @@ func newValidatorCommands() []cli.Command { Flags: append([]cli.Flag{ walletPathFlag, walletConfigFlag, - gasFlag, - sysGasFlag, - outFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.OutFlag, + txctx.ForceFlag, flags.AddressFlag{ Name: "address, a", Usage: "Address to vote from", @@ -132,7 +133,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w if err != nil { return cli.NewExitError(err, 1) } - return signAndSendSomeTransaction(ctx, act, acc, tx) + return txctx.SignAndSend(ctx, act, acc, tx) } func handleVote(ctx *cli.Context) error { diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 9e1c1e29f..1e5695ba1 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -14,6 +14,7 @@ import ( "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/txctx" "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -64,10 +65,6 @@ var ( Name: "decrypt, d", Usage: "Decrypt encrypted keys.", } - outFlag = cli.StringFlag{ - Name: "out", - Usage: "file to put JSON transaction to", - } inFlag = cli.StringFlag{ Name: "in", Usage: "file with JSON transaction", @@ -80,10 +77,6 @@ var ( Name: "to", Usage: "Address to send an asset to", } - forceFlag = cli.BoolFlag{ - Name: "force", - Usage: "Do not ask for a confirmation", - } ) // NewCommands returns 'wallet' command. @@ -91,10 +84,10 @@ func NewCommands() []cli.Command { claimFlags := []cli.Flag{ walletPathFlag, walletConfigFlag, - gasFlag, - sysGasFlag, - outFlag, - forceFlag, + txctx.GasFlag, + txctx.SysGasFlag, + txctx.OutFlag, + txctx.ForceFlag, flags.AddressFlag{ Name: "address, a", Usage: "Address to claim GAS for", @@ -104,7 +97,7 @@ func NewCommands() []cli.Command { signFlags := []cli.Flag{ walletPathFlag, walletConfigFlag, - outFlag, + txctx.OutFlag, inFlag, flags.AddressFlag{ Name: "address, a", @@ -287,7 +280,7 @@ func NewCommands() []cli.Command { Flags: []cli.Flag{ walletPathFlag, walletConfigFlag, - forceFlag, + txctx.ForceFlag, flags.AddressFlag{ Name: "address, a", Usage: "Account address or hash in LE form to be removed", @@ -322,7 +315,7 @@ func NewCommands() []cli.Command { Flags: []cli.Flag{ walletPathFlag, walletConfigFlag, - forceFlag, + txctx.ForceFlag, }, }, {