From 3631a7957244457c449df9de98d974696550988e Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Mon, 27 Jun 2022 19:54:39 +0800 Subject: [PATCH 01/11] [ioctl] build action command line into new ioctl --- ioctl/newcmd/action/action.go | 374 ++++++++++++++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 ioctl/newcmd/action/action.go diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go new file mode 100644 index 0000000000..8c5a9d44fb --- /dev/null +++ b/ioctl/newcmd/action/action.go @@ -0,0 +1,374 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no +// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent +// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache +// License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "context" + "encoding/hex" + "fmt" + "math/big" + "strings" + + "github.com/grpc-ecosystem/go-grpc-middleware/util/metautils" + "github.com/iotexproject/go-pkgs/hash" + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + + "github.com/iotexproject/iotex-core/action" + "github.com/iotexproject/iotex-core/ioctl" + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/flag" + "github.com/iotexproject/iotex-core/ioctl/newcmd/account" + "github.com/iotexproject/iotex-core/ioctl/newcmd/bc" + "github.com/iotexproject/iotex-core/ioctl/util" + "github.com/iotexproject/iotex-core/pkg/util/byteutil" +) + +// Multi-language support +var ( + _actionCmdShorts = map[config.Language]string{ + config.English: "Manage actions of IoTeX blockchain", + config.Chinese: "管理IoTex区块链的行为", // this translation + } + _actionCmdUses = map[config.Language]string{ + config.English: "action", + config.Chinese: "action 行为", // this translation + } +) + +const _defaultGasLimit = uint64(20000000) + +// Flags +var ( + _gasLimitFlag = flag.NewUint64VarP("gas-limit", "l", _defaultGasLimit, "set gas limit") + _gasPriceFlag = flag.NewStringVarP("gas-price", "p", "1", "set gas price (unit: 10^(-6)IOTX), use suggested gas price if input is \"0\"") + _nonceFlag = flag.NewUint64VarP("nonce", "n", 0, "set nonce (default using pending nonce)") + _signerFlag = flag.NewStringVarP("signer", "s", "", "choose a signing account") + _yesFlag = flag.BoolVarP("assume-yes", "y", false, "answer yes for all confirmations") + _passwordFlag = flag.NewStringVarP("password", "P", "", "input password for account") +) + +// NewActionCmd represents the node delegate command +func NewActionCmd(client ioctl.Client) *cobra.Command { + use, _ := client.SelectTranslation(_actionCmdUses) + short, _ := client.SelectTranslation(_actionCmdShorts) + + act := &cobra.Command{ + Use: use, + Short: short, + } + + client.SetEndpointWithFlag(act.PersistentFlags().StringVar) + client.SetInsecureWithFlag(act.PersistentFlags().BoolVar) + + return act +} + +// Signer returns signer's address +func Signer(client ioctl.Client) (address string, err error) { + addressOrAlias := _signerFlag.Value().(string) + if util.AliasIsHdwalletKey(addressOrAlias) { + return addressOrAlias, nil + } + + if addressOrAlias == "" { + addressOrAlias, err = config.GetContextAddressOrAlias() + if err != nil { + return + } + } + return client.Address(addressOrAlias) +} + +func nonce(client ioctl.Client, executor string) (uint64, error) { + if util.AliasIsHdwalletKey(executor) { + // for hdwallet key, get the nonce in SendAction() + return 0, nil + } + nonce := _nonceFlag.Value().(uint64) + if nonce != 0 { + return nonce, nil + } + accountMeta, err := account.Meta(client, executor) + if err != nil { + return 0, errors.New("failed to get account meta") + } + return accountMeta.PendingNonce, nil +} + +// RegisterWriteCommand registers action flags for command +func RegisterWriteCommand(cmd *cobra.Command) { + _gasLimitFlag.RegisterCommand(cmd) + _gasPriceFlag.RegisterCommand(cmd) + _signerFlag.RegisterCommand(cmd) + _nonceFlag.RegisterCommand(cmd) + _yesFlag.RegisterCommand(cmd) + _passwordFlag.RegisterCommand(cmd) +} + +// gasPriceInRau returns the suggest gas price +func gasPriceInRau(client ioctl.Client) (*big.Int, error) { + if client.IsCryptoSm2() { + return big.NewInt(0), nil + } + gasPrice := _gasPriceFlag.Value().(string) + if len(gasPrice) != 0 { + return util.StringToRau(gasPrice, util.GasPriceDecimalNum) + } + apiServiceClient, err := client.APIServiceClient() + if err != nil { + return nil, err + } + ctx := context.Background() + + jwtMD, err := util.JwtAuth() + if err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + request := &iotexapi.SuggestGasPriceRequest{} + response, err := apiServiceClient.SuggestGasPrice(ctx, request) + if err != nil { + sta, ok := status.FromError(err) + if ok { + return nil, errors.New(sta.Message()) + } + return nil, errors.New("failed to invoke SuggestGasPrice api") + } + return new(big.Int).SetUint64(response.GasPrice), nil +} + +func fixGasLimit(client ioctl.Client, caller string, execution *action.Execution) (*action.Execution, error) { + apiServiceClient, err := client.APIServiceClient() + if err != nil { + return nil, err + } + request := &iotexapi.EstimateActionGasConsumptionRequest{ + Action: &iotexapi.EstimateActionGasConsumptionRequest_Execution{ + Execution: execution.Proto(), + }, + CallerAddress: caller, + } + + ctx := context.Background() + jwtMD, err := util.JwtAuth() + if err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + res, err := apiServiceClient.EstimateActionGasConsumption(ctx, request) + if err != nil { + sta, ok := status.FromError(err) + if ok { + return nil, errors.New(sta.Message()) + } + return nil, errors.New("failed to invoke EstimateActionGasConsumption api") + } + return action.NewExecution(execution.Contract(), execution.Nonce(), execution.Amount(), res.Gas, execution.GasPrice(), execution.Data()) +} + +// SendRaw sends raw action to blockchain +func SendRaw(client ioctl.Client, selp *iotextypes.Action) error { + apiServiceClient, err := client.APIServiceClient() + if err != nil { + return err + } + ctx := context.Background() + + jwtMD, err := util.JwtAuth() + if err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + request := &iotexapi.SendActionRequest{Action: selp} + if _, err = apiServiceClient.SendAction(ctx, request); err != nil { + if sta, ok := status.FromError(err); ok { + return errors.New(sta.Message()) + } + return errors.New("failed to invoke SendAction api") + } + shash := hash.Hash256b(byteutil.Must(proto.Marshal(selp))) + txhash := hex.EncodeToString(shash[:]) + URL := "https://" + switch config.ReadConfig.Explorer { + case "iotexscan": + if strings.Contains(config.ReadConfig.Endpoint, "testnet") { + URL += "testnet." + } + URL += "iotexscan.io/action/" + txhash + case "iotxplorer": + URL = "iotxplorer.io/actions/" + txhash + default: + URL = config.ReadConfig.Explorer + txhash + } + fmt.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s", URL) + return nil +} + +// SendAction sends signed action to blockchain +func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, signer string) error { + prvKey, err := account.PrivateKeyFromSigner(client, cmd, signer, _passwordFlag.Value().(string)) + if err != nil { + return err + } + + chainMeta, err := bc.GetChainMeta(client) + if err != nil { + return errors.Wrap(err, "failed to get chain meta") + } + elp.SetChainID(chainMeta.GetChainID()) + + if util.AliasIsHdwalletKey(signer) { + addr := prvKey.PublicKey().Address() + signer = addr.String() + nonce, err := nonce(client, signer) + if err != nil { + return errors.Wrap(err, "failed to get nonce ") + } + elp.SetNonce(nonce) + } + + sealed, err := action.Sign(elp, prvKey) + prvKey.Zero() + if err != nil { + return errors.Wrap(err, "failed to sign action") + } + if err := isBalanceEnough(client, signer, sealed); err != nil { + return errors.Wrap(err, "failed to pass balance check") + } + + selp := sealed.Proto() + actionInfo, err := printActionProto(client, selp) + if err != nil { + return errors.Wrap(err, "failed to print action proto message") + } + + if _yesFlag.Value() == false { + var confirm string + info := fmt.Sprintln(actionInfo + "\nPlease confirm your action.\n") + line := fmt.Sprintf("%s\nOptions:", info) + for _, option := range []string{"yes"} { + line += " " + option + } + line += "\nQuit for anything else." + fmt.Println(line) + + confirm, err := client.ReadInput() + if err != nil { + return err + } + if !client.AskToConfirm(confirm) { + cmd.Println("quit") + return nil + } + } + + return SendRaw(client, selp) +} + +// Execute sends signed execution transaction to blockchain +func Execute(client ioctl.Client, cmd *cobra.Command, contract string, amount *big.Int, bytecode []byte) error { + if len(contract) == 0 && len(bytecode) == 0 { + return errors.New("failed to deploy contract with empty bytecode") + } + gasPriceRau, err := gasPriceInRau(client) + if err != nil { + return errors.New("failed to get gas price") + } + signer, err := Signer(client) + if err != nil { + return errors.New("failed to get signer address") + } + nonce, err := nonce(client, signer) + if err != nil { + return errors.New("failed to get nonce") + } + gasLimit := _gasLimitFlag.Value().(uint64) + tx, err := action.NewExecution(contract, nonce, amount, gasLimit, gasPriceRau, bytecode) + if err != nil || tx == nil { + return errors.New("failed to make a Execution instance") + } + if gasLimit == 0 { + tx, err = fixGasLimit(client, signer, tx) + if err != nil || tx == nil { + return errors.New("failed to fix Execution gaslimit") + } + gasLimit = tx.GasLimit() + } + return SendAction( + client, + cmd, + (&action.EnvelopeBuilder{}). + SetNonce(nonce). + SetGasPrice(gasPriceRau). + SetGasLimit(gasLimit). + SetAction(tx).Build(), + signer, + ) +} + +// Read reads smart contract on IoTeX blockchain +func Read(client ioctl.Client, contract address.Address, amount string, bytecode []byte) (string, error) { + apiServiceClient, err := client.APIServiceClient() + if err != nil { + return "", err + } + + ctx := context.Background() + jwtMD, err := util.JwtAuth() + if err == nil { + ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) + } + + callerAddr, _ := Signer(client) + if callerAddr == "" { + callerAddr = address.ZeroAddress + } + res, err := apiServiceClient.ReadContract( + ctx, + &iotexapi.ReadContractRequest{ + Execution: &iotextypes.Execution{ + Amount: amount, + Contract: contract.String(), + Data: bytecode, + }, + CallerAddress: callerAddr, + GasLimit: _gasLimitFlag.Value().(uint64), + }, + ) + if err == nil { + return res.Data, nil + } + if sta, ok := status.FromError(err); ok { + return "", errors.New(sta.Message()) + } + return "", errors.New("failed to invoke ReadContract api") +} + +func isBalanceEnough(client ioctl.Client, address string, act action.SealedEnvelope) error { + accountMeta, err := account.Meta(client, address) + if err != nil { + return errors.New("failed to get account meta") + } + balance, ok := new(big.Int).SetString(accountMeta.Balance, 10) + if !ok { + return errors.New("failed to convert balance into big int") + } + cost, err := act.Cost() + if err != nil { + return errors.New("failed to check cost of an action") + } + if balance.Cmp(cost) < 0 { + return errors.New("balance is not enough") + } + return nil +} From 47a7cfde49f9d85e0ec4d4ba39617cb0761d6893 Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Tue, 28 Jun 2022 03:03:51 +0800 Subject: [PATCH 02/11] refactor unit test to cover the modification --- ioctl/newcmd/action/action.go | 17 +-- ioctl/newcmd/action/action_test.go | 163 +++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 ioctl/newcmd/action/action_test.go diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index 8c5a9d44fb..8f051267ac 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -81,9 +81,9 @@ func Signer(client ioctl.Client) (address string, err error) { } if addressOrAlias == "" { - addressOrAlias, err = config.GetContextAddressOrAlias() - if err != nil { - return + defaultAccount := client.Config().DefaultAccount + if strings.EqualFold(defaultAccount.AddressOrAlias, "") { + return "", errors.New("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first") } } return client.Address(addressOrAlias) @@ -199,16 +199,16 @@ func SendRaw(client ioctl.Client, selp *iotextypes.Action) error { shash := hash.Hash256b(byteutil.Must(proto.Marshal(selp))) txhash := hex.EncodeToString(shash[:]) URL := "https://" - switch config.ReadConfig.Explorer { + switch client.Config().Explorer { case "iotexscan": - if strings.Contains(config.ReadConfig.Endpoint, "testnet") { + if strings.Contains(client.Config().Endpoint, "testnet") { URL += "testnet." } URL += "iotexscan.io/action/" + txhash case "iotxplorer": URL = "iotxplorer.io/actions/" + txhash default: - URL = config.ReadConfig.Explorer + txhash + URL = client.Config().Explorer + txhash } fmt.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s", URL) return nil @@ -329,7 +329,10 @@ func Read(client ioctl.Client, contract address.Address, amount string, bytecode ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) } - callerAddr, _ := Signer(client) + callerAddr, err := Signer(client) + if err != nil { + return "", err + } if callerAddr == "" { callerAddr = address.ZeroAddress } diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go new file mode 100644 index 0000000000..e72fa12de5 --- /dev/null +++ b/ioctl/newcmd/action/action_test.go @@ -0,0 +1,163 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. This source code is provided 'as is' and no +// warranties are given as to title or non-infringement, merchantability or fitness for purpose and, to the extent +// permitted by law, all liability for your use of the code is disclaimed. This source code is governed by Apache +// License 2.0 that can be found in the LICENSE file. + +package action + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/iotexproject/iotex-core/action" + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/pkg/unit" + "github.com/iotexproject/iotex-core/test/identityset" + "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" +) + +func TestSigner(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + + t.Run("returns signer's address", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + DefaultAccount: config.Context{ + AddressOrAlias: "test", + }, + }) + client.EXPECT().Address(gomock.Any()).Return("test", nil) + + result, err := Signer(client) + require.NoError(err) + require.Equal(result, "test") + }) + + t.Run("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first", func(t *testing.T) { + expectedErr := errors.New("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first") + + client.EXPECT().Config().Return(config.Config{}) + + _, err := Signer(client) + require.Equal(err.Error(), expectedErr.Error()) + }) +} + +func TestSendRaw(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + selp := &iotextypes.Action{} + response := &iotexapi.SendActionResponse{} + + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(4) + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) + + t.Run("sends raw action to blockchain", func(t *testing.T) { + t.Run("endpoint iotexscan", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotexscan", + Endpoint: "testnet1", + }).Times(2) + + err := SendRaw(client, selp) + require.NoError(err) + }) + + t.Run("endpoint iotxplorer", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotxplorer", + }) + + err := SendRaw(client, selp) + require.NoError(err) + }) + + t.Run("endpoint default", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "test", + }).Times(2) + + err := SendRaw(client, selp) + require.NoError(err) + }) + }) + + t.Run("failed to invoke SendAction api", func(t *testing.T) { + expectedErr := errors.New("failed to invoke SendAction api") + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + + err := SendRaw(client, selp) + require.Equal(err.Error(), expectedErr.Error()) + }) +} + +func TestSendAction(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + response := &iotexapi.SendActionResponse{} + tx := &action.Execution{} + elp := (&action.EnvelopeBuilder{}). + SetNonce(0). + SetGasPrice(unit.ConvertIotxToRau(1100000)). + SetGasLimit(100). + SetAction(tx).Build() + + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(2) + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil) + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil) + + t.Run("sends signed action to blockchain", func(t *testing.T) { + cmd := NewActionCmd(client) + err := SendAction(client, cmd, elp, "test") + require.Error(err) // This should be NoError, but couldn't design a correct signer with public key. + }) +} + +func TestRead(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + contractAddr := identityset.Address(28) + + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2) + client.EXPECT().Config().Return(config.Config{ + DefaultAccount: config.Context{ + AddressOrAlias: "test", + }, + }).Times(2) + client.EXPECT().Address(gomock.Any()).Return("test", nil).Times(2) + + t.Run("reads smart contract on IoTeX blockchain", func(t *testing.T) { + response := &iotexapi.ReadContractResponse{ + Data: "test", + } + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(response, nil) + + result, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) + require.NoError(err) + require.Equal(result, "test") + }) + + t.Run("failed to invoke ReadContract api", func(t *testing.T) { + expectedErr := errors.New("failed to invoke ReadContract api") + + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + + _, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) + require.Equal(err.Error(), expectedErr.Error()) + }) +} From 41d14461bf4710e42f0a6a220c5a991dcc2cf246 Mon Sep 17 00:00:00 2001 From: huof6890 <68298506@qq.com> Date: Tue, 28 Jun 2022 12:40:41 +0800 Subject: [PATCH 03/11] modify TestSendAction --- ioctl/newcmd/action/action_test.go | 66 ++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 16 deletions(-) diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go index e72fa12de5..b46e5e31b8 100644 --- a/ioctl/newcmd/action/action_test.go +++ b/ioctl/newcmd/action/action_test.go @@ -7,19 +7,27 @@ package action import ( + "os" "testing" + "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/golang/mock/gomock" - "github.com/iotexproject/iotex-core/action" - "github.com/iotexproject/iotex-core/ioctl/config" - "github.com/iotexproject/iotex-core/pkg/unit" - "github.com/iotexproject/iotex-core/test/identityset" - "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" + "github.com/iotexproject/iotex-address/address" "github.com/iotexproject/iotex-proto/golang/iotexapi" "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "github.com/stretchr/testify/require" + + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/test/identityset" + "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" + "github.com/iotexproject/iotex-core/testutil" +) + +const ( + veryLightScryptN = 2 + veryLightScryptP = 1 ) func TestSigner(t *testing.T) { @@ -105,24 +113,50 @@ func TestSendAction(t *testing.T) { ctrl := gomock.NewController(t) client := mock_ioctlclient.NewMockClient(ctrl) apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) - response := &iotexapi.SendActionResponse{} - tx := &action.Execution{} - elp := (&action.EnvelopeBuilder{}). - SetNonce(0). - SetGasPrice(unit.ConvertIotxToRau(1100000)). - SetGasLimit(100). - SetAction(tx).Build() client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(2) client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) - client.EXPECT().APIServiceClient().Return(apiServiceClient, nil) - apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil) + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(3) + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(&iotexapi.SendActionResponse{}, nil) + + client.EXPECT().IsCryptoSm2().Return(false).Times(2) + testWallet, err := os.MkdirTemp(os.TempDir(), "testWallet") + require.NoError(err) + defer testutil.CleanupPath(testWallet) + ks := keystore.NewKeyStore(testWallet, veryLightScryptN, veryLightScryptP) + client.EXPECT().NewKeyStore().Return(ks).Times(2) + + passwd := "123456" + client.EXPECT().ReadSecret().Return(passwd, nil).Times(2) + account, err := ks.NewAccount(passwd) + require.NoError(err) + addr, err := address.FromBytes(account.Address.Bytes()) + require.NoError(err) + client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil) + + chainMetaResponse := &iotexapi.GetChainMetaResponse{ChainMeta: &iotextypes.ChainMeta{}} + apiServiceClient.EXPECT().GetChainMeta(gomock.Any(), gomock.Any()).Return(chainMetaResponse, nil) + + elp := createEnvelope(0) + cost, err := elp.Cost() + require.NoError(err) + accountResponse := &iotexapi.GetAccountResponse{AccountMeta: &iotextypes.AccountMeta{ + Address: addr.String(), + Nonce: 1, + PendingNonce: 1, + Balance: cost.String(), + }} + apiServiceClient.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Return(accountResponse, nil) + + client.EXPECT().Alias(gomock.Any()).Return("producer", nil).Times(2) + client.EXPECT().ReadInput().Return("confirm", nil) + client.EXPECT().AskToConfirm(gomock.Any()).Return(false) t.Run("sends signed action to blockchain", func(t *testing.T) { cmd := NewActionCmd(client) - err := SendAction(client, cmd, elp, "test") - require.Error(err) // This should be NoError, but couldn't design a correct signer with public key. + err := SendAction(client, cmd, elp, addr.String()) + require.NoError(err) }) } From d0e5c023a936dc684674dc8ac6e7a593a33bb10f Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Wed, 29 Jun 2022 18:11:00 +0800 Subject: [PATCH 04/11] refer to action.go in #3444 --- ioctl/newcmd/action/action.go | 451 ++++++++++++++++++++++------------ 1 file changed, 298 insertions(+), 153 deletions(-) diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index 8f051267ac..8aca59613a 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -9,7 +9,7 @@ package action import ( "context" "encoding/hex" - "fmt" + "encoding/json" "math/big" "strings" @@ -43,180 +43,331 @@ var ( config.English: "action", config.Chinese: "action 行为", // this translation } + _infoWarn = map[config.Language]string{ + config.English: "** This is an irreversible action!\n" + + "Once an account is deleted, all the assets under this account may be lost!\n" + + "Type 'YES' to continue, quit for anything else.", + config.Chinese: "** 这是一个不可逆转的操作!\n" + + "一旦一个账户被删除, 该账户下的所有资源都可能会丢失!\n" + + "输入 'YES' 以继续, 否则退出", + } + _infoQuit = map[config.Language]string{ + config.English: "quit", + config.Chinese: "退出", + } + _flagActionEndPointUsages = map[config.Language]string{ + config.English: "set endpoint for once", + config.Chinese: "一次设置端点", // this translation + } + _flagActionInsecureUsages = map[config.Language]string{ + config.English: "insecure connection for once", + config.Chinese: "一次不安全连接", // this translation + } + _flagGasLimitUsages = map[config.Language]string{ + config.English: "set gas limit", + } + _flagGasPriceUsages = map[config.Language]string{ + config.English: `set gas price (unit: 10^(-6)IOTX), use suggested gas price if input is "0"`, + } + _flagNonceUsages = map[config.Language]string{ + config.English: "set nonce (default using pending nonce)", + } + _flagSignerUsages = map[config.Language]string{ + config.English: "choose a signing account", + } + _flagBytecodeUsages = map[config.Language]string{ + config.English: "set the byte code", + } + _flagAssumeYesUsages = map[config.Language]string{ + config.English: "answer yes for all confirmations", + } + _flagPasswordUsages = map[config.Language]string{ + config.English: "input password for account", + } ) -const _defaultGasLimit = uint64(20000000) - -// Flags -var ( - _gasLimitFlag = flag.NewUint64VarP("gas-limit", "l", _defaultGasLimit, "set gas limit") - _gasPriceFlag = flag.NewStringVarP("gas-price", "p", "1", "set gas price (unit: 10^(-6)IOTX), use suggested gas price if input is \"0\"") - _nonceFlag = flag.NewUint64VarP("nonce", "n", 0, "set nonce (default using pending nonce)") - _signerFlag = flag.NewStringVarP("signer", "s", "", "choose a signing account") - _yesFlag = flag.BoolVarP("assume-yes", "y", false, "answer yes for all confirmations") - _passwordFlag = flag.NewStringVarP("password", "P", "", "input password for account") +// Flag label, short label and defaults +const ( + gasLimitFlagLabel = "gas-limit" + gasLimitFlagShortLabel = "l" + gasLimitFlagDefault = uint64(20000000) + gasPriceFlagLabel = "gas-price" + gasPriceFlagShortLabel = "p" + gasPriceFlagDefault = "1" + nonceFlagLabel = "nonce" + nonceFlagShortLabel = "n" + nonceFlagDefault = uint64(0) + signerFlagLabel = "signer" + signerFlagShortLabel = "s" + signerFlagDefault = "" + bytecodeFlagLabel = "bytecode" + bytecodeFlagShortLabel = "b" + bytecodeFlagDefault = "" + assumeYesFlagLabel = "assume-yes" + assumeYesFlagShortLabel = "y" + assumeYesFlagDefault = false + passwordFlagLabel = "password" + passwordFlagShortLabel = "P" + passwordFlagDefault = "" ) -// NewActionCmd represents the node delegate command -func NewActionCmd(client ioctl.Client) *cobra.Command { - use, _ := client.SelectTranslation(_actionCmdUses) - short, _ := client.SelectTranslation(_actionCmdShorts) +func registerGasLimitFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagGasLimitUsages) + flag.NewUint64VarP(gasLimitFlagLabel, gasLimitFlagShortLabel, gasLimitFlagDefault, usage).RegisterCommand(cmd) +} + +func registerGasPriceFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagGasPriceUsages) + flag.NewStringVarP(gasPriceFlagLabel, gasPriceFlagShortLabel, gasPriceFlagDefault, usage).RegisterCommand(cmd) +} + +func registerNonceFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagNonceUsages) + flag.NewUint64VarP(nonceFlagLabel, nonceFlagShortLabel, nonceFlagDefault, usage).RegisterCommand(cmd) +} + +func registerSignerFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagSignerUsages) + flag.NewStringVarP(signerFlagLabel, signerFlagShortLabel, signerFlagDefault, usage).RegisterCommand(cmd) +} + +func registerBytecodeFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagBytecodeUsages) + flag.NewStringVarP(bytecodeFlagLabel, bytecodeFlagShortLabel, bytecodeFlagDefault, usage).RegisterCommand(cmd) +} + +func registerAssumeYesFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagAssumeYesUsages) + flag.BoolVarP(assumeYesFlagLabel, assumeYesFlagShortLabel, assumeYesFlagDefault, usage).RegisterCommand(cmd) +} + +func registerPasswordFlag(client ioctl.Client, cmd *cobra.Command) { + usage, _ := client.SelectTranslation(_flagPasswordUsages) + flag.NewStringVarP(passwordFlagLabel, passwordFlagShortLabel, passwordFlagDefault, usage).RegisterCommand(cmd) +} + +func mustString(v string, err error) string { + if err != nil { + panic(err) + } + return v +} + +func mustUint64(v uint64, err error) uint64 { + if err != nil { + panic(err) + } + return v +} + +func mustBoolean(v bool, err error) bool { + if err != nil { + panic(err) + } + return v +} - act := &cobra.Command{ - Use: use, - Short: short, +func getGasLimitFlagValue(cmd *cobra.Command) (v uint64) { + return mustUint64(cmd.Flags().GetUint64(gasLimitFlagLabel)) +} + +func getGasPriceFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(gasPriceFlagLabel)) +} + +func getNonceFlagValue(cmd *cobra.Command) (v uint64) { + return mustUint64(cmd.Flags().GetUint64(nonceFlagLabel)) +} + +func getSignerFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(signerFlagLabel)) +} + +func getBytecodeFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(bytecodeFlagLabel)) +} + +func getDecodeBytecode(cmd *cobra.Command) ([]byte, error) { + bytecode, err := cmd.Flags().GetString(bytecodeFlagLabel) + if err != nil { + return nil, err } + return hex.DecodeString(util.TrimHexPrefix(bytecode)) +} - client.SetEndpointWithFlag(act.PersistentFlags().StringVar) - client.SetInsecureWithFlag(act.PersistentFlags().BoolVar) +func getAssumeYesFlagValue(cmd *cobra.Command) (v bool) { + return mustBoolean(cmd.Flags().GetBool(assumeYesFlagLabel)) +} + +func getPasswordFlagValue(cmd *cobra.Command) (v string) { + return mustString(cmd.Flags().GetString(passwordFlagLabel)) +} + +func selectTranslation(client ioctl.Client, trls map[config.Language]string) string { + txt, _ := client.SelectTranslation(trls) + return txt +} + +// NewActionCmd represents the action command +func NewActionCmd(client ioctl.Client) *cobra.Command { + cmd := &cobra.Command{ + Use: selectTranslation(client, _actionCmdUses), + Short: selectTranslation(client, _actionCmdShorts), + } + + // TODO add sub commands + // cmd.AddCommand(NewActionHash(client)) + // cmd.AddCommand(NewActionTransfer(client)) + // cmd.AddCommand(NewActionDeploy(client)) + // cmd.AddCommand(NewActionInvoke(client)) + // cmd.AddCommand(NewActionRead(client)) + // cmd.AddCommand(NewActionClaim(client)) + // cmd.AddCommand(NewActionDeposit(client)) + // cmd.AddCommand(NewActionSendRaw(client)) + + var ( + insecure bool + endpoint string + ) + cmd.PersistentFlags().StringVar(&endpoint, "endpoint", client.Config().Endpoint, selectTranslation(client, _flagActionEndPointUsages)) + cmd.PersistentFlags().BoolVar(&insecure, "insecure", !client.Config().SecureConnect, selectTranslation(client, _flagActionInsecureUsages)) + + return cmd +} + +// RegisterWriteCommand registers action flags for command +func RegisterWriteCommand(client ioctl.Client, cmd *cobra.Command) { + registerGasLimitFlag(client, cmd) + registerGasPriceFlag(client, cmd) + registerSignerFlag(client, cmd) + registerNonceFlag(client, cmd) + registerAssumeYesFlag(client, cmd) + registerPasswordFlag(client, cmd) +} - return act +func handleClientRequestError(err error, apiName string) error { + sta, ok := status.FromError(err) + if ok { + return errors.New(sta.Message()) + } + return errors.Wrapf(err, "failed to invoke %s api", apiName) } // Signer returns signer's address -func Signer(client ioctl.Client) (address string, err error) { - addressOrAlias := _signerFlag.Value().(string) +func Signer(client ioctl.Client, cmd *cobra.Command) (address string, err error) { + addressOrAlias := getSignerFlagValue(cmd) if util.AliasIsHdwalletKey(addressOrAlias) { return addressOrAlias, nil } - - if addressOrAlias == "" { - defaultAccount := client.Config().DefaultAccount - if strings.EqualFold(defaultAccount.AddressOrAlias, "") { - return "", errors.New("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first") - } - } - return client.Address(addressOrAlias) + return client.AddressWithDefaultIfNotExist(addressOrAlias) } -func nonce(client ioctl.Client, executor string) (uint64, error) { +func nonce(client ioctl.Client, cmd *cobra.Command, executor string) (uint64, error) { if util.AliasIsHdwalletKey(executor) { // for hdwallet key, get the nonce in SendAction() return 0, nil } - nonce := _nonceFlag.Value().(uint64) - if nonce != 0 { + if nonce := getNonceFlagValue(cmd); nonce != 0 { return nonce, nil } accountMeta, err := account.Meta(client, executor) if err != nil { - return 0, errors.New("failed to get account meta") + return 0, errors.Wrap(err, "failed to get account meta") } return accountMeta.PendingNonce, nil } -// RegisterWriteCommand registers action flags for command -func RegisterWriteCommand(cmd *cobra.Command) { - _gasLimitFlag.RegisterCommand(cmd) - _gasPriceFlag.RegisterCommand(cmd) - _signerFlag.RegisterCommand(cmd) - _nonceFlag.RegisterCommand(cmd) - _yesFlag.RegisterCommand(cmd) - _passwordFlag.RegisterCommand(cmd) -} - // gasPriceInRau returns the suggest gas price -func gasPriceInRau(client ioctl.Client) (*big.Int, error) { +func gasPriceInRau(client ioctl.Client, cmd *cobra.Command) (*big.Int, error) { if client.IsCryptoSm2() { return big.NewInt(0), nil } - gasPrice := _gasPriceFlag.Value().(string) + gasPrice := getGasPriceFlagValue(cmd) if len(gasPrice) != 0 { return util.StringToRau(gasPrice, util.GasPriceDecimalNum) } - apiServiceClient, err := client.APIServiceClient() + + cli, err := client.APIServiceClient() if err != nil { - return nil, err + return nil, errors.Wrap(err, "failed to connect to endpoint") } - ctx := context.Background() - jwtMD, err := util.JwtAuth() - if err == nil { + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) } - request := &iotexapi.SuggestGasPriceRequest{} - response, err := apiServiceClient.SuggestGasPrice(ctx, request) + rsp, err := cli.SuggestGasPrice(ctx, &iotexapi.SuggestGasPriceRequest{}) if err != nil { - sta, ok := status.FromError(err) - if ok { - return nil, errors.New(sta.Message()) - } - return nil, errors.New("failed to invoke SuggestGasPrice api") + return nil, handleClientRequestError(err, "SuggestGasPrice") } - return new(big.Int).SetUint64(response.GasPrice), nil + return new(big.Int).SetUint64(rsp.GasPrice), nil } func fixGasLimit(client ioctl.Client, caller string, execution *action.Execution) (*action.Execution, error) { - apiServiceClient, err := client.APIServiceClient() + cli, err := client.APIServiceClient() if err != nil { - return nil, err - } - request := &iotexapi.EstimateActionGasConsumptionRequest{ - Action: &iotexapi.EstimateActionGasConsumptionRequest_Execution{ - Execution: execution.Proto(), - }, - CallerAddress: caller, + return nil, errors.Wrap(err, "failed to connect to endpoint") } ctx := context.Background() - jwtMD, err := util.JwtAuth() - if err == nil { + if jwtMD, err := util.JwtAuth(); err == nil { ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) } - res, err := apiServiceClient.EstimateActionGasConsumption(ctx, request) + res, err := cli.EstimateActionGasConsumption(ctx, + &iotexapi.EstimateActionGasConsumptionRequest{ + Action: &iotexapi.EstimateActionGasConsumptionRequest_Execution{ + Execution: execution.Proto(), + }, + CallerAddress: caller, + }) if err != nil { - sta, ok := status.FromError(err) - if ok { - return nil, errors.New(sta.Message()) - } - return nil, errors.New("failed to invoke EstimateActionGasConsumption api") + return nil, handleClientRequestError(err, "EstimateActionGasConsumption") } return action.NewExecution(execution.Contract(), execution.Nonce(), execution.Amount(), res.Gas, execution.GasPrice(), execution.Data()) } // SendRaw sends raw action to blockchain -func SendRaw(client ioctl.Client, selp *iotextypes.Action) error { - apiServiceClient, err := client.APIServiceClient() +func SendRaw(client ioctl.Client, cmd *cobra.Command, selp *iotextypes.Action) error { + cli, err := client.APIServiceClient() if err != nil { - return err + return errors.Wrap(err, "failed to connect to endpoint") } - ctx := context.Background() - jwtMD, err := util.JwtAuth() - if err == nil { + ctx := context.Background() + if jwtMD, err := util.JwtAuth(); err == nil { ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) } - request := &iotexapi.SendActionRequest{Action: selp} - if _, err = apiServiceClient.SendAction(ctx, request); err != nil { - if sta, ok := status.FromError(err); ok { - return errors.New(sta.Message()) - } - return errors.New("failed to invoke SendAction api") + _, err = cli.SendAction(ctx, &iotexapi.SendActionRequest{Action: selp}) + if err != nil { + return handleClientRequestError(err, "SendAction") } + shash := hash.Hash256b(byteutil.Must(proto.Marshal(selp))) txhash := hex.EncodeToString(shash[:]) - URL := "https://" - switch client.Config().Explorer { + message := sendMessage{Info: "Action has been sent to blockchain.", TxHash: txhash, URL: "https://"} + endpoint := client.Config().Endpoint + explorer := client.Config().Explorer + switch explorer { case "iotexscan": - if strings.Contains(client.Config().Endpoint, "testnet") { - URL += "testnet." + if strings.Contains(endpoint, "testnet") { + message.URL += "testnet." } - URL += "iotexscan.io/action/" + txhash + message.URL += "iotexscan.io/action/" + txhash case "iotxplorer": - URL = "iotxplorer.io/actions/" + txhash + message.URL = "iotxplorer.io/actions/" + txhash default: - URL = client.Config().Explorer + txhash + message.URL = explorer + txhash } - fmt.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s", URL) + cmd.Println(message.String()) return nil } // SendAction sends signed action to blockchain func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, signer string) error { - prvKey, err := account.PrivateKeyFromSigner(client, cmd, signer, _passwordFlag.Value().(string)) + sk, err := account.PrivateKeyFromSigner(client, cmd, signer, getPasswordFlagValue(cmd)) if err != nil { return err } @@ -228,17 +379,16 @@ func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, si elp.SetChainID(chainMeta.GetChainID()) if util.AliasIsHdwalletKey(signer) { - addr := prvKey.PublicKey().Address() + addr := sk.PublicKey().Address() signer = addr.String() - nonce, err := nonce(client, signer) + nonce, err := nonce(client, cmd, signer) if err != nil { return errors.Wrap(err, "failed to get nonce ") } elp.SetNonce(nonce) } - sealed, err := action.Sign(elp, prvKey) - prvKey.Zero() + sealed, err := action.Sign(elp, sk) if err != nil { return errors.Wrap(err, "failed to sign action") } @@ -247,32 +397,24 @@ func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, si } selp := sealed.Proto() - actionInfo, err := printActionProto(client, selp) - if err != nil { - return errors.Wrap(err, "failed to print action proto message") - } - - if _yesFlag.Value() == false { - var confirm string - info := fmt.Sprintln(actionInfo + "\nPlease confirm your action.\n") - line := fmt.Sprintf("%s\nOptions:", info) - for _, option := range []string{"yes"} { - line += " " + option - } - line += "\nQuit for anything else." - fmt.Println(line) - - confirm, err := client.ReadInput() - if err != nil { - return err - } - if !client.AskToConfirm(confirm) { - cmd.Println("quit") - return nil + sk.Zero() + // TODO wait newcmd/action/actionhash impl pr #3425 + // actionInfo, err := printActionProto(selp) + // if err != nil { + // return errors.Wrap(err, "failed to print action proto message") + // } + // cmd.Println(actionInfo) + + if getAssumeYesFlagValue(cmd) == false { + infoWarn := selectTranslation(client, _infoWarn) + infoQuit := selectTranslation(client, _infoQuit) + if !client.AskToConfirm(infoWarn) { + cmd.Println(infoQuit) } + return nil } - return SendRaw(client, selp) + return SendRaw(client, cmd, selp) } // Execute sends signed execution transaction to blockchain @@ -280,27 +422,27 @@ func Execute(client ioctl.Client, cmd *cobra.Command, contract string, amount *b if len(contract) == 0 && len(bytecode) == 0 { return errors.New("failed to deploy contract with empty bytecode") } - gasPriceRau, err := gasPriceInRau(client) + gasPriceRau, err := gasPriceInRau(client, cmd) if err != nil { - return errors.New("failed to get gas price") + return errors.Wrap(err, "failed to get gas price") } - signer, err := Signer(client) + signer, err := Signer(client, cmd) if err != nil { - return errors.New("failed to get signer address") + return errors.Wrap(err, "failed to get signer address") } - nonce, err := nonce(client, signer) + nonce, err := nonce(client, cmd, signer) if err != nil { - return errors.New("failed to get nonce") + return errors.Wrap(err, "failed to get nonce") } - gasLimit := _gasLimitFlag.Value().(uint64) + gasLimit := getGasLimitFlagValue(cmd) tx, err := action.NewExecution(contract, nonce, amount, gasLimit, gasPriceRau, bytecode) if err != nil || tx == nil { - return errors.New("failed to make a Execution instance") + return errors.Wrap(err, "failed to make a Execution instance") } if gasLimit == 0 { tx, err = fixGasLimit(client, signer, tx) if err != nil || tx == nil { - return errors.New("failed to fix Execution gaslimit") + return errors.Wrap(err, "failed to fix Execution gas limit") } gasLimit = tx.GasLimit() } @@ -317,27 +459,23 @@ func Execute(client ioctl.Client, cmd *cobra.Command, contract string, amount *b } // Read reads smart contract on IoTeX blockchain -func Read(client ioctl.Client, contract address.Address, amount string, bytecode []byte) (string, error) { - apiServiceClient, err := client.APIServiceClient() +func Read(client ioctl.Client, cmd *cobra.Command, contract address.Address, amount string, bytecode []byte) (string, error) { + cli, err := client.APIServiceClient() if err != nil { - return "", err + return "", errors.Wrap(err, "failed to connect to endpoint") } ctx := context.Background() - jwtMD, err := util.JwtAuth() - if err == nil { + if jwtMD, err := util.JwtAuth(); err == nil { ctx = metautils.NiceMD(jwtMD).ToOutgoing(ctx) } - callerAddr, err := Signer(client) - if err != nil { - return "", err - } + callerAddr, _ := Signer(client, cmd) if callerAddr == "" { callerAddr = address.ZeroAddress } - res, err := apiServiceClient.ReadContract( - ctx, + + res, err := cli.ReadContract(ctx, &iotexapi.ReadContractRequest{ Execution: &iotextypes.Execution{ Amount: amount, @@ -345,22 +483,19 @@ func Read(client ioctl.Client, contract address.Address, amount string, bytecode Data: bytecode, }, CallerAddress: callerAddr, - GasLimit: _gasLimitFlag.Value().(uint64), + GasLimit: getGasLimitFlagValue(cmd), }, ) - if err == nil { - return res.Data, nil - } - if sta, ok := status.FromError(err); ok { - return "", errors.New(sta.Message()) + if err != nil { + return "", handleClientRequestError(err, "ReadContract") } - return "", errors.New("failed to invoke ReadContract api") + return res.Data, nil } func isBalanceEnough(client ioctl.Client, address string, act action.SealedEnvelope) error { accountMeta, err := account.Meta(client, address) if err != nil { - return errors.New("failed to get account meta") + return errors.Wrap(err, "failed to get account meta") } balance, ok := new(big.Int).SetString(accountMeta.Balance, 10) if !ok { @@ -368,10 +503,20 @@ func isBalanceEnough(client ioctl.Client, address string, act action.SealedEnvel } cost, err := act.Cost() if err != nil { - return errors.New("failed to check cost of an action") + return errors.Wrap(err, "failed to check cost of an action") } if balance.Cmp(cost) < 0 { return errors.New("balance is not enough") } return nil } + +type sendMessage struct { + Info string `json:"info"` + TxHash string `json:"txHash"` + URL string `json:"url"` +} + +func (m *sendMessage) String() string { + return string(byteutil.Must(json.MarshalIndent(m, "", " "))) +} From db2fd317de363a89671479fdde8402791ac8139f Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Wed, 29 Jun 2022 19:04:39 +0800 Subject: [PATCH 05/11] refactor unit test for new action.go --- ioctl/newcmd/action/action_test.go | 337 ++++++++++++++++------------- 1 file changed, 182 insertions(+), 155 deletions(-) diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go index b46e5e31b8..0eda8c11c8 100644 --- a/ioctl/newcmd/action/action_test.go +++ b/ioctl/newcmd/action/action_test.go @@ -7,22 +7,14 @@ package action import ( - "os" "testing" - "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/golang/mock/gomock" - "github.com/iotexproject/iotex-address/address" - "github.com/iotexproject/iotex-proto/golang/iotexapi" - "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" - "github.com/iotexproject/iotex-proto/golang/iotextypes" - "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/iotexproject/iotex-core/ioctl/config" - "github.com/iotexproject/iotex-core/test/identityset" + "github.com/iotexproject/iotex-core/ioctl/util" "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" - "github.com/iotexproject/iotex-core/testutil" ) const ( @@ -35,6 +27,9 @@ func TestSigner(t *testing.T) { ctrl := gomock.NewController(t) client := mock_ioctlclient.NewMockClient(ctrl) + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(4) + client.EXPECT().Config().Return(config.Config{}).Times(4) + t.Run("returns signer's address", func(t *testing.T) { client.EXPECT().Config().Return(config.Config{ DefaultAccount: config.Context{ @@ -43,155 +38,187 @@ func TestSigner(t *testing.T) { }) client.EXPECT().Address(gomock.Any()).Return("test", nil) - result, err := Signer(client) - require.NoError(err) - require.Equal(result, "test") - }) - - t.Run("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first", func(t *testing.T) { - expectedErr := errors.New("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first") - - client.EXPECT().Config().Return(config.Config{}) - - _, err := Signer(client) - require.Equal(err.Error(), expectedErr.Error()) - }) -} - -func TestSendRaw(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - client := mock_ioctlclient.NewMockClient(ctrl) - apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) - selp := &iotextypes.Action{} - response := &iotexapi.SendActionResponse{} - - client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(4) - apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) - - t.Run("sends raw action to blockchain", func(t *testing.T) { - t.Run("endpoint iotexscan", func(t *testing.T) { - client.EXPECT().Config().Return(config.Config{ - Explorer: "iotexscan", - Endpoint: "testnet1", - }).Times(2) - - err := SendRaw(client, selp) - require.NoError(err) - }) - - t.Run("endpoint iotxplorer", func(t *testing.T) { - client.EXPECT().Config().Return(config.Config{ - Explorer: "iotxplorer", - }) - - err := SendRaw(client, selp) - require.NoError(err) - }) - - t.Run("endpoint default", func(t *testing.T) { - client.EXPECT().Config().Return(config.Config{ - Explorer: "test", - }).Times(2) - - err := SendRaw(client, selp) - require.NoError(err) - }) - }) - - t.Run("failed to invoke SendAction api", func(t *testing.T) { - expectedErr := errors.New("failed to invoke SendAction api") - apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) - - err := SendRaw(client, selp) - require.Equal(err.Error(), expectedErr.Error()) - }) -} - -func TestSendAction(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - client := mock_ioctlclient.NewMockClient(ctrl) - apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) - - client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(2) - client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) - client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) - client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(3) - apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(&iotexapi.SendActionResponse{}, nil) - - client.EXPECT().IsCryptoSm2().Return(false).Times(2) - testWallet, err := os.MkdirTemp(os.TempDir(), "testWallet") - require.NoError(err) - defer testutil.CleanupPath(testWallet) - ks := keystore.NewKeyStore(testWallet, veryLightScryptN, veryLightScryptP) - client.EXPECT().NewKeyStore().Return(ks).Times(2) - - passwd := "123456" - client.EXPECT().ReadSecret().Return(passwd, nil).Times(2) - account, err := ks.NewAccount(passwd) - require.NoError(err) - addr, err := address.FromBytes(account.Address.Bytes()) - require.NoError(err) - client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil) - - chainMetaResponse := &iotexapi.GetChainMetaResponse{ChainMeta: &iotextypes.ChainMeta{}} - apiServiceClient.EXPECT().GetChainMeta(gomock.Any(), gomock.Any()).Return(chainMetaResponse, nil) - - elp := createEnvelope(0) - cost, err := elp.Cost() - require.NoError(err) - accountResponse := &iotexapi.GetAccountResponse{AccountMeta: &iotextypes.AccountMeta{ - Address: addr.String(), - Nonce: 1, - PendingNonce: 1, - Balance: cost.String(), - }} - apiServiceClient.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Return(accountResponse, nil) - - client.EXPECT().Alias(gomock.Any()).Return("producer", nil).Times(2) - client.EXPECT().ReadInput().Return("confirm", nil) - client.EXPECT().AskToConfirm(gomock.Any()).Return(false) - - t.Run("sends signed action to blockchain", func(t *testing.T) { cmd := NewActionCmd(client) - err := SendAction(client, cmd, elp, addr.String()) - require.NoError(err) - }) -} - -func TestRead(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - client := mock_ioctlclient.NewMockClient(ctrl) - apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) - contractAddr := identityset.Address(28) - - client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2) - client.EXPECT().Config().Return(config.Config{ - DefaultAccount: config.Context{ - AddressOrAlias: "test", - }, - }).Times(2) - client.EXPECT().Address(gomock.Any()).Return("test", nil).Times(2) - - t.Run("reads smart contract on IoTeX blockchain", func(t *testing.T) { - response := &iotexapi.ReadContractResponse{ - Data: "test", - } - apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(response, nil) - - result, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) - require.NoError(err) - require.Equal(result, "test") + _, err := util.ExecuteCmd(cmd, "--signer", "test") + require.Error(err) + // result, err := Signer(client, cmd) + // require.NoError(err) + // require.Equal(result, "test") }) - t.Run("failed to invoke ReadContract api", func(t *testing.T) { - expectedErr := errors.New("failed to invoke ReadContract api") + // t.Run("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first", func(t *testing.T) { + // expectedErr := errors.New("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first") - apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + // client.EXPECT().Config().Return(config.Config{}) - _, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) - require.Equal(err.Error(), expectedErr.Error()) - }) + // cmd := NewActionCmd(client) + // _, err := Signer(client, cmd) + // require.Equal(err.Error(), expectedErr.Error()) + // }) } + +// func TestSendRaw(t *testing.T) { +// require := require.New(t) +// ctrl := gomock.NewController(t) +// client := mock_ioctlclient.NewMockClient(ctrl) +// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) +// selp := &iotextypes.Action{} +// response := &iotexapi.SendActionResponse{} + +// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(4) +// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) + +// t.Run("sends raw action to blockchain", func(t *testing.T) { +// t.Run("endpoint iotexscan", func(t *testing.T) { +// client.EXPECT().Config().Return(config.Config{ +// Explorer: "iotexscan", +// Endpoint: "testnet1", +// }).Times(2) + +// err := SendRaw(client, selp) +// require.NoError(err) +// }) + +// t.Run("endpoint iotxplorer", func(t *testing.T) { +// client.EXPECT().Config().Return(config.Config{ +// Explorer: "iotxplorer", +// }) + +// err := SendRaw(client, selp) +// require.NoError(err) +// }) + +// t.Run("endpoint default", func(t *testing.T) { +// client.EXPECT().Config().Return(config.Config{ +// Explorer: "test", +// }).Times(2) + +// err := SendRaw(client, selp) +// require.NoError(err) +// }) +// }) + +// t.Run("failed to invoke SendAction api", func(t *testing.T) { +// expectedErr := errors.New("failed to invoke SendAction api") +// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + +// err := SendRaw(client, selp) +// require.Equal(err.Error(), expectedErr.Error()) +// }) +// } + +// func TestSendAction(t *testing.T) { +// require := require.New(t) +// ctrl := gomock.NewController(t) +// client := mock_ioctlclient.NewMockClient(ctrl) +// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) +// testWallet, err := os.MkdirTemp(os.TempDir(), "testWallet") +// require.NoError(err) +// defer testutil.CleanupPath(testWallet) + +// ks := keystore.NewKeyStore(testWallet, veryLightScryptN, veryLightScryptP) +// passwd := "123456" +// account, err := ks.NewAccount(passwd) +// require.NoError(err) +// addr, err := address.FromBytes(account.Address.Bytes()) +// require.NoError(err) +// chainMetaResponse := &iotexapi.GetChainMetaResponse{ChainMeta: &iotextypes.ChainMeta{}} +// elp := createEnvelope(0) +// cost, err := elp.Cost() +// require.NoError(err) +// accountResponse := &iotexapi.GetAccountResponse{AccountMeta: &iotextypes.AccountMeta{ +// Address: addr.String(), +// Nonce: 1, +// PendingNonce: 1, +// Balance: cost.String(), +// }} + +// client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(2) +// client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) +// client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) +// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(3) +// client.EXPECT().IsCryptoSm2().Return(false).Times(2) +// client.EXPECT().NewKeyStore().Return(ks).Times(2) +// client.EXPECT().ReadSecret().Return(passwd, nil).Times(2) +// client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil) +// client.EXPECT().Alias(gomock.Any()).Return("producer", nil).Times(2) +// client.EXPECT().ReadInput().Return("confirm", nil) +// client.EXPECT().AskToConfirm(gomock.Any()).Return(false) + +// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(&iotexapi.SendActionResponse{}, nil) +// apiServiceClient.EXPECT().GetChainMeta(gomock.Any(), gomock.Any()).Return(chainMetaResponse, nil) +// apiServiceClient.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Return(accountResponse, nil) + +// t.Run("sends signed action to blockchain", func(t *testing.T) { +// cmd := NewActionCmd(client) +// err := SendAction(client, cmd, elp, addr.String()) +// require.NoError(err) +// }) + +// t.Run("failed to get chain meta", func(t *testing.T) { + +// }) + +// t.Run("failed to get nonce", func(t *testing.T) { + +// }) + +// t.Run("failed to sign action", func(t *testing.T) { + +// }) + +// t.Run("failed to pass balance check", func(t *testing.T) { + +// }) + +// t.Run("failed to print action proto message", func(t *testing.T) { + +// }) +// } + +// func TestExecute(t *testing.T) { +// // require := require.New(t) +// ctrl := gomock.NewController(t) +// client := mock_ioctlclient.NewMockClient(ctrl) +// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + +// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil) + +// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(&iotexapi.SendActionResponse{}, nil) +// } + +// func TestRead(t *testing.T) { +// require := require.New(t) +// ctrl := gomock.NewController(t) +// client := mock_ioctlclient.NewMockClient(ctrl) +// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) +// contractAddr := identityset.Address(28) + +// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2) +// client.EXPECT().Config().Return(config.Config{ +// DefaultAccount: config.Context{ +// AddressOrAlias: "test", +// }, +// }).Times(2) +// client.EXPECT().Address(gomock.Any()).Return("test", nil).Times(2) + +// t.Run("reads smart contract on IoTeX blockchain", func(t *testing.T) { +// response := &iotexapi.ReadContractResponse{ +// Data: "test", +// } +// apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(response, nil) + +// result, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) +// require.NoError(err) +// require.Equal(result, "test") +// }) + +// t.Run("failed to invoke ReadContract api", func(t *testing.T) { +// expectedErr := errors.New("failed to invoke ReadContract api") + +// apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + +// _, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) +// require.Equal(err.Error(), expectedErr.Error()) +// }) +// } From 62ebd933c83bcbef9e4594cb6e3964b8ea107d7b Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Thu, 30 Jun 2022 23:45:58 +0800 Subject: [PATCH 06/11] fix commit --- ioctl/newcmd/action/action.go | 51 +++++++++++++++-------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index 8aca59613a..0f6fb56a66 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -20,6 +20,7 @@ import ( "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "github.com/spf13/cobra" + "go.uber.org/zap" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" @@ -30,6 +31,7 @@ import ( "github.com/iotexproject/iotex-core/ioctl/newcmd/account" "github.com/iotexproject/iotex-core/ioctl/newcmd/bc" "github.com/iotexproject/iotex-core/ioctl/util" + "github.com/iotexproject/iotex-core/pkg/log" "github.com/iotexproject/iotex-core/pkg/util/byteutil" ) @@ -55,34 +57,33 @@ var ( config.English: "quit", config.Chinese: "退出", } - _flagActionEndPointUsages = map[config.Language]string{ - config.English: "set endpoint for once", - config.Chinese: "一次设置端点", // this translation - } - _flagActionInsecureUsages = map[config.Language]string{ - config.English: "insecure connection for once", - config.Chinese: "一次不安全连接", // this translation - } _flagGasLimitUsages = map[config.Language]string{ config.English: "set gas limit", + config.Chinese: "设置燃气上限", } _flagGasPriceUsages = map[config.Language]string{ config.English: `set gas price (unit: 10^(-6)IOTX), use suggested gas price if input is "0"`, + config.Chinese: `设置燃气费(单位:10^(-6)IOTX),如果输入为「0」,则使用默认燃气费`, } _flagNonceUsages = map[config.Language]string{ config.English: "set nonce (default using pending nonce)", + config.Chinese: "设置 nonce (默认使用 pending nonce)", } _flagSignerUsages = map[config.Language]string{ config.English: "choose a signing account", + config.Chinese: "选择要签名的帐户", } _flagBytecodeUsages = map[config.Language]string{ config.English: "set the byte code", + config.Chinese: "设置字节码", } _flagAssumeYesUsages = map[config.Language]string{ config.English: "answer yes for all confirmations", + config.Chinese: "为所有确认设置 yes", } _flagPasswordUsages = map[config.Language]string{ config.English: "input password for account", + config.Chinese: "设置密码", } ) @@ -148,30 +149,30 @@ func registerPasswordFlag(client ioctl.Client, cmd *cobra.Command) { func mustString(v string, err error) string { if err != nil { - panic(err) + log.L().Panic("input flag must be string", zap.Error(err)) } return v } func mustUint64(v uint64, err error) uint64 { if err != nil { - panic(err) + log.L().Panic("input flag must be uint64", zap.Error(err)) } return v } func mustBoolean(v bool, err error) bool { if err != nil { - panic(err) + log.L().Panic("input flag must be boolean", zap.Error(err)) } return v } -func getGasLimitFlagValue(cmd *cobra.Command) (v uint64) { +func gasLimitFlagValue(cmd *cobra.Command) (v uint64) { return mustUint64(cmd.Flags().GetUint64(gasLimitFlagLabel)) } -func getGasPriceFlagValue(cmd *cobra.Command) (v string) { +func gasPriceFlagValue(cmd *cobra.Command) (v string) { return mustString(cmd.Flags().GetString(gasPriceFlagLabel)) } @@ -188,11 +189,7 @@ func getBytecodeFlagValue(cmd *cobra.Command) (v string) { } func getDecodeBytecode(cmd *cobra.Command) ([]byte, error) { - bytecode, err := cmd.Flags().GetString(bytecodeFlagLabel) - if err != nil { - return nil, err - } - return hex.DecodeString(util.TrimHexPrefix(bytecode)) + return hex.DecodeString(util.TrimHexPrefix(getBytecodeFlagValue(cmd))) } func getAssumeYesFlagValue(cmd *cobra.Command) (v bool) { @@ -210,7 +207,7 @@ func selectTranslation(client ioctl.Client, trls map[config.Language]string) str // NewActionCmd represents the action command func NewActionCmd(client ioctl.Client) *cobra.Command { - cmd := &cobra.Command{ + ac := &cobra.Command{ Use: selectTranslation(client, _actionCmdUses), Short: selectTranslation(client, _actionCmdShorts), } @@ -225,14 +222,10 @@ func NewActionCmd(client ioctl.Client) *cobra.Command { // cmd.AddCommand(NewActionDeposit(client)) // cmd.AddCommand(NewActionSendRaw(client)) - var ( - insecure bool - endpoint string - ) - cmd.PersistentFlags().StringVar(&endpoint, "endpoint", client.Config().Endpoint, selectTranslation(client, _flagActionEndPointUsages)) - cmd.PersistentFlags().BoolVar(&insecure, "insecure", !client.Config().SecureConnect, selectTranslation(client, _flagActionInsecureUsages)) + client.SetEndpointWithFlag(ac.PersistentFlags().StringVar) + client.SetInsecureWithFlag(ac.PersistentFlags().BoolVar) - return cmd + return ac } // RegisterWriteCommand registers action flags for command @@ -282,7 +275,7 @@ func gasPriceInRau(client ioctl.Client, cmd *cobra.Command) (*big.Int, error) { if client.IsCryptoSm2() { return big.NewInt(0), nil } - gasPrice := getGasPriceFlagValue(cmd) + gasPrice := gasPriceFlagValue(cmd) if len(gasPrice) != 0 { return util.StringToRau(gasPrice, util.GasPriceDecimalNum) } @@ -434,7 +427,7 @@ func Execute(client ioctl.Client, cmd *cobra.Command, contract string, amount *b if err != nil { return errors.Wrap(err, "failed to get nonce") } - gasLimit := getGasLimitFlagValue(cmd) + gasLimit := gasLimitFlagValue(cmd) tx, err := action.NewExecution(contract, nonce, amount, gasLimit, gasPriceRau, bytecode) if err != nil || tx == nil { return errors.Wrap(err, "failed to make a Execution instance") @@ -483,7 +476,7 @@ func Read(client ioctl.Client, cmd *cobra.Command, contract address.Address, amo Data: bytecode, }, CallerAddress: callerAddr, - GasLimit: getGasLimitFlagValue(cmd), + GasLimit: gasLimitFlagValue(cmd), }, ) if err != nil { From a2f3c601ce8dfd7740f6796d3efb016331acb2dc Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Fri, 1 Jul 2022 01:08:20 +0800 Subject: [PATCH 07/11] fix commit --- ioctl/newcmd/action/action.go | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index 0f6fb56a66..3ad23d9aec 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -9,7 +9,7 @@ package action import ( "context" "encoding/hex" - "encoding/json" + "fmt" "math/big" "strings" @@ -340,21 +340,21 @@ func SendRaw(client ioctl.Client, cmd *cobra.Command, selp *iotextypes.Action) e shash := hash.Hash256b(byteutil.Must(proto.Marshal(selp))) txhash := hex.EncodeToString(shash[:]) - message := sendMessage{Info: "Action has been sent to blockchain.", TxHash: txhash, URL: "https://"} + URL := "https://" endpoint := client.Config().Endpoint explorer := client.Config().Explorer switch explorer { case "iotexscan": if strings.Contains(endpoint, "testnet") { - message.URL += "testnet." + URL += "testnet." } - message.URL += "iotexscan.io/action/" + txhash + URL += "iotexscan.io/action/" + txhash case "iotxplorer": - message.URL = "iotxplorer.io/actions/" + txhash + URL = "iotxplorer.io/actions/" + txhash default: - message.URL = explorer + txhash + URL = explorer + txhash } - cmd.Println(message.String()) + fmt.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s", URL) return nil } @@ -398,7 +398,7 @@ func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, si // } // cmd.Println(actionInfo) - if getAssumeYesFlagValue(cmd) == false { + if !getAssumeYesFlagValue(cmd) { infoWarn := selectTranslation(client, _infoWarn) infoQuit := selectTranslation(client, _infoQuit) if !client.AskToConfirm(infoWarn) { @@ -503,13 +503,3 @@ func isBalanceEnough(client ioctl.Client, address string, act action.SealedEnvel } return nil } - -type sendMessage struct { - Info string `json:"info"` - TxHash string `json:"txHash"` - URL string `json:"url"` -} - -func (m *sendMessage) String() string { - return string(byteutil.Must(json.MarshalIndent(m, "", " "))) -} From 289791c3e8cbad0bce7aced2723ec581a4d4da5c Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Thu, 7 Jul 2022 00:21:14 +0800 Subject: [PATCH 08/11] build unit test to cover the modification --- ioctl/newcmd/action/action.go | 4 +- ioctl/newcmd/action/action_test.go | 299 +++++++++++------------------ 2 files changed, 112 insertions(+), 191 deletions(-) diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index 3ad23d9aec..f50edd7778 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -322,7 +322,7 @@ func fixGasLimit(client ioctl.Client, caller string, execution *action.Execution } // SendRaw sends raw action to blockchain -func SendRaw(client ioctl.Client, cmd *cobra.Command, selp *iotextypes.Action) error { +func SendRaw(client ioctl.Client, selp *iotextypes.Action) error { cli, err := client.APIServiceClient() if err != nil { return errors.Wrap(err, "failed to connect to endpoint") @@ -407,7 +407,7 @@ func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, si return nil } - return SendRaw(client, cmd, selp) + return SendRaw(client, selp) } // Execute sends signed execution transaction to blockchain diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go index 0eda8c11c8..e77aaf619a 100644 --- a/ioctl/newcmd/action/action_test.go +++ b/ioctl/newcmd/action/action_test.go @@ -10,11 +10,15 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/iotexproject/iotex-core/ioctl/config" "github.com/iotexproject/iotex-core/ioctl/util" "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" ) const ( @@ -22,203 +26,120 @@ const ( veryLightScryptP = 1 ) +var ( + testData = []struct { + endpoint string + insecure bool + }{ + { + endpoint: "111:222:333:444:5678", + insecure: false, + }, + { + endpoint: "", + insecure: true, + }, + } +) + func TestSigner(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) client := mock_ioctlclient.NewMockClient(ctrl) - client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(4) - client.EXPECT().Config().Return(config.Config{}).Times(4) + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(6) + + for _, test := range testData { + callbackEndpoint := func(cb func(*string, string, string, string)) { + cb(&test.endpoint, "endpoint", test.endpoint, "endpoint usage") + } + callbackInsecure := func(cb func(*bool, string, bool, string)) { + cb(&test.insecure, "insecure", !test.insecure, "insecure usage") + } + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure) + + t.Run("returns signer's address", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + DefaultAccount: config.Context{ + AddressOrAlias: "test", + }, + }) + client.EXPECT().Address(gomock.Any()).Return("test", nil) + client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return("test", nil).AnyTimes() + + cmd := NewActionCmd(client) + registerSignerFlag(client, cmd) + _, err := util.ExecuteCmd(cmd, "--signer", "test") + require.NoError(err) + result, err := Signer(client, cmd) + require.NoError(err) + require.Equal(result, "test") + }) + } +} - t.Run("returns signer's address", func(t *testing.T) { - client.EXPECT().Config().Return(config.Config{ - DefaultAccount: config.Context{ - AddressOrAlias: "test", - }, +func TestSendRaw(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + selp := &iotextypes.Action{} + + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(12) + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(7) + + for _, test := range testData { + callbackEndpoint := func(cb func(*string, string, string, string)) { + cb(&test.endpoint, "endpoint", test.endpoint, "endpoint usage") + } + callbackInsecure := func(cb func(*bool, string, bool, string)) { + cb(&test.insecure, "insecure", !test.insecure, "insecure usage") + } + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure) + + t.Run("sends raw action to blockchain", func(t *testing.T) { + response := &iotexapi.SendActionResponse{} + + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) + + t.Run("endpoint iotexscan", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotexscan", + Endpoint: "testnet1", + }).Times(2) + + err := SendRaw(client, selp) + require.NoError(err) + }) + + t.Run("endpoint iotxplorer", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotxplorer", + }).Times(2) + + err := SendRaw(client, selp) + require.NoError(err) + }) + + t.Run("endpoint default", func(t *testing.T) { + client.EXPECT().Config().Return(config.Config{ + Explorer: "test", + }).Times(2) + + err := SendRaw(client, selp) + require.NoError(err) + }) }) - client.EXPECT().Address(gomock.Any()).Return("test", nil) - - cmd := NewActionCmd(client) - _, err := util.ExecuteCmd(cmd, "--signer", "test") - require.Error(err) - // result, err := Signer(client, cmd) - // require.NoError(err) - // require.Equal(result, "test") - }) + } - // t.Run("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first", func(t *testing.T) { - // expectedErr := errors.New("use 'ioctl config set defaultacc ADDRESS|ALIAS' to config default account first") + t.Run("failed to invoke SendAction api", func(t *testing.T) { + expectedErr := errors.New("failed to invoke SendAction api") - // client.EXPECT().Config().Return(config.Config{}) + apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) - // cmd := NewActionCmd(client) - // _, err := Signer(client, cmd) - // require.Equal(err.Error(), expectedErr.Error()) - // }) + err := SendRaw(client, selp) + require.Contains(err.Error(), expectedErr.Error()) + }) } - -// func TestSendRaw(t *testing.T) { -// require := require.New(t) -// ctrl := gomock.NewController(t) -// client := mock_ioctlclient.NewMockClient(ctrl) -// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) -// selp := &iotextypes.Action{} -// response := &iotexapi.SendActionResponse{} - -// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(4) -// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) - -// t.Run("sends raw action to blockchain", func(t *testing.T) { -// t.Run("endpoint iotexscan", func(t *testing.T) { -// client.EXPECT().Config().Return(config.Config{ -// Explorer: "iotexscan", -// Endpoint: "testnet1", -// }).Times(2) - -// err := SendRaw(client, selp) -// require.NoError(err) -// }) - -// t.Run("endpoint iotxplorer", func(t *testing.T) { -// client.EXPECT().Config().Return(config.Config{ -// Explorer: "iotxplorer", -// }) - -// err := SendRaw(client, selp) -// require.NoError(err) -// }) - -// t.Run("endpoint default", func(t *testing.T) { -// client.EXPECT().Config().Return(config.Config{ -// Explorer: "test", -// }).Times(2) - -// err := SendRaw(client, selp) -// require.NoError(err) -// }) -// }) - -// t.Run("failed to invoke SendAction api", func(t *testing.T) { -// expectedErr := errors.New("failed to invoke SendAction api") -// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) - -// err := SendRaw(client, selp) -// require.Equal(err.Error(), expectedErr.Error()) -// }) -// } - -// func TestSendAction(t *testing.T) { -// require := require.New(t) -// ctrl := gomock.NewController(t) -// client := mock_ioctlclient.NewMockClient(ctrl) -// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) -// testWallet, err := os.MkdirTemp(os.TempDir(), "testWallet") -// require.NoError(err) -// defer testutil.CleanupPath(testWallet) - -// ks := keystore.NewKeyStore(testWallet, veryLightScryptN, veryLightScryptP) -// passwd := "123456" -// account, err := ks.NewAccount(passwd) -// require.NoError(err) -// addr, err := address.FromBytes(account.Address.Bytes()) -// require.NoError(err) -// chainMetaResponse := &iotexapi.GetChainMetaResponse{ChainMeta: &iotextypes.ChainMeta{}} -// elp := createEnvelope(0) -// cost, err := elp.Cost() -// require.NoError(err) -// accountResponse := &iotexapi.GetAccountResponse{AccountMeta: &iotextypes.AccountMeta{ -// Address: addr.String(), -// Nonce: 1, -// PendingNonce: 1, -// Balance: cost.String(), -// }} - -// client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(2) -// client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) -// client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) -// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(3) -// client.EXPECT().IsCryptoSm2().Return(false).Times(2) -// client.EXPECT().NewKeyStore().Return(ks).Times(2) -// client.EXPECT().ReadSecret().Return(passwd, nil).Times(2) -// client.EXPECT().Address(gomock.Any()).Return(addr.String(), nil) -// client.EXPECT().Alias(gomock.Any()).Return("producer", nil).Times(2) -// client.EXPECT().ReadInput().Return("confirm", nil) -// client.EXPECT().AskToConfirm(gomock.Any()).Return(false) - -// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(&iotexapi.SendActionResponse{}, nil) -// apiServiceClient.EXPECT().GetChainMeta(gomock.Any(), gomock.Any()).Return(chainMetaResponse, nil) -// apiServiceClient.EXPECT().GetAccount(gomock.Any(), gomock.Any()).Return(accountResponse, nil) - -// t.Run("sends signed action to blockchain", func(t *testing.T) { -// cmd := NewActionCmd(client) -// err := SendAction(client, cmd, elp, addr.String()) -// require.NoError(err) -// }) - -// t.Run("failed to get chain meta", func(t *testing.T) { - -// }) - -// t.Run("failed to get nonce", func(t *testing.T) { - -// }) - -// t.Run("failed to sign action", func(t *testing.T) { - -// }) - -// t.Run("failed to pass balance check", func(t *testing.T) { - -// }) - -// t.Run("failed to print action proto message", func(t *testing.T) { - -// }) -// } - -// func TestExecute(t *testing.T) { -// // require := require.New(t) -// ctrl := gomock.NewController(t) -// client := mock_ioctlclient.NewMockClient(ctrl) -// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) - -// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil) - -// apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(&iotexapi.SendActionResponse{}, nil) -// } - -// func TestRead(t *testing.T) { -// require := require.New(t) -// ctrl := gomock.NewController(t) -// client := mock_ioctlclient.NewMockClient(ctrl) -// apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) -// contractAddr := identityset.Address(28) - -// client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(2) -// client.EXPECT().Config().Return(config.Config{ -// DefaultAccount: config.Context{ -// AddressOrAlias: "test", -// }, -// }).Times(2) -// client.EXPECT().Address(gomock.Any()).Return("test", nil).Times(2) - -// t.Run("reads smart contract on IoTeX blockchain", func(t *testing.T) { -// response := &iotexapi.ReadContractResponse{ -// Data: "test", -// } -// apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(response, nil) - -// result, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) -// require.NoError(err) -// require.Equal(result, "test") -// }) - -// t.Run("failed to invoke ReadContract api", func(t *testing.T) { -// expectedErr := errors.New("failed to invoke ReadContract api") - -// apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(nil, expectedErr) - -// _, err := Read(client, contractAddr, "0", []byte("0x1bfc56c600000000000000000000000000000000000000000000000000000000000000")) -// require.Equal(err.Error(), expectedErr.Error()) -// }) -// } From c3d3bb6b0fab6f400bffa2aa68917c559f03371d Mon Sep 17 00:00:00 2001 From: huof6890 <68298506@qq.com> Date: Thu, 7 Jul 2022 20:32:41 +0800 Subject: [PATCH 09/11] modify TestSinger for clearly --- ioctl/newcmd/action/action_test.go | 52 +++++++++++------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go index e77aaf619a..520a93b10b 100644 --- a/ioctl/newcmd/action/action_test.go +++ b/ioctl/newcmd/action/action_test.go @@ -10,15 +10,15 @@ import ( "testing" "github.com/golang/mock/gomock" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotextypes" "github.com/pkg/errors" "github.com/stretchr/testify/require" "github.com/iotexproject/iotex-core/ioctl/config" "github.com/iotexproject/iotex-core/ioctl/util" "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" - "github.com/iotexproject/iotex-proto/golang/iotexapi" - "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" - "github.com/iotexproject/iotex-proto/golang/iotextypes" ) const ( @@ -46,37 +46,21 @@ func TestSigner(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) client := mock_ioctlclient.NewMockClient(ctrl) - - client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(6) - - for _, test := range testData { - callbackEndpoint := func(cb func(*string, string, string, string)) { - cb(&test.endpoint, "endpoint", test.endpoint, "endpoint usage") - } - callbackInsecure := func(cb func(*bool, string, bool, string)) { - cb(&test.insecure, "insecure", !test.insecure, "insecure usage") - } - client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint) - client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure) - - t.Run("returns signer's address", func(t *testing.T) { - client.EXPECT().Config().Return(config.Config{ - DefaultAccount: config.Context{ - AddressOrAlias: "test", - }, - }) - client.EXPECT().Address(gomock.Any()).Return("test", nil) - client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return("test", nil).AnyTimes() - - cmd := NewActionCmd(client) - registerSignerFlag(client, cmd) - _, err := util.ExecuteCmd(cmd, "--signer", "test") - require.NoError(err) - result, err := Signer(client, cmd) - require.NoError(err) - require.Equal(result, "test") - }) - } + client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(3) + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(func(_ func(*string, string, string, string)) {}) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(func(_ func(*bool, string, bool, string)) {}) + + t.Run("returns signer's address", func(t *testing.T) { + client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return("test", nil).AnyTimes() + + cmd := NewActionCmd(client) + registerSignerFlag(client, cmd) + _, err := util.ExecuteCmd(cmd, "--signer", "test") + require.NoError(err) + result, err := Signer(client, cmd) + require.NoError(err) + require.Equal(result, "test") + }) } func TestSendRaw(t *testing.T) { From a3f6b666f5cda73427f030a908e4e9eea5c33d20 Mon Sep 17 00:00:00 2001 From: LuckyPigeon Date: Tue, 12 Jul 2022 06:07:58 +0800 Subject: [PATCH 10/11] add cmd parameter for SendRaw func --- ioctl/newcmd/action/action.go | 7 +++---- ioctl/newcmd/action/action_test.go | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index f50edd7778..b1696563ec 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -9,7 +9,6 @@ package action import ( "context" "encoding/hex" - "fmt" "math/big" "strings" @@ -322,7 +321,7 @@ func fixGasLimit(client ioctl.Client, caller string, execution *action.Execution } // SendRaw sends raw action to blockchain -func SendRaw(client ioctl.Client, selp *iotextypes.Action) error { +func SendRaw(client ioctl.Client, cmd *cobra.Command, selp *iotextypes.Action) error { cli, err := client.APIServiceClient() if err != nil { return errors.Wrap(err, "failed to connect to endpoint") @@ -354,7 +353,7 @@ func SendRaw(client ioctl.Client, selp *iotextypes.Action) error { default: URL = explorer + txhash } - fmt.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s", URL) + cmd.Printf("Action has been sent to blockchain.\nWait for several seconds and query this action by hash: %s\n", URL) return nil } @@ -407,7 +406,7 @@ func SendAction(client ioctl.Client, cmd *cobra.Command, elp action.Envelope, si return nil } - return SendRaw(client, selp) + return SendRaw(client, cmd, selp) } // Execute sends signed execution transaction to blockchain diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go index 520a93b10b..6daea7afa9 100644 --- a/ioctl/newcmd/action/action_test.go +++ b/ioctl/newcmd/action/action_test.go @@ -80,21 +80,25 @@ func TestSendRaw(t *testing.T) { callbackInsecure := func(cb func(*bool, string, bool, string)) { cb(&test.insecure, "insecure", !test.insecure, "insecure usage") } - client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint) - client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure) + client.EXPECT().SetEndpointWithFlag(gomock.Any()).Do(callbackEndpoint).Times(3) + client.EXPECT().SetInsecureWithFlag(gomock.Any()).Do(callbackInsecure).Times(3) t.Run("sends raw action to blockchain", func(t *testing.T) { response := &iotexapi.SendActionResponse{} apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(response, nil).Times(3) + cmd := NewActionCmd(client) + _, err := util.ExecuteCmd(cmd) + require.NoError(err) + t.Run("endpoint iotexscan", func(t *testing.T) { client.EXPECT().Config().Return(config.Config{ Explorer: "iotexscan", Endpoint: "testnet1", }).Times(2) - err := SendRaw(client, selp) + err = SendRaw(client, cmd, selp) require.NoError(err) }) @@ -103,7 +107,7 @@ func TestSendRaw(t *testing.T) { Explorer: "iotxplorer", }).Times(2) - err := SendRaw(client, selp) + err := SendRaw(client, cmd, selp) require.NoError(err) }) @@ -112,7 +116,7 @@ func TestSendRaw(t *testing.T) { Explorer: "test", }).Times(2) - err := SendRaw(client, selp) + err := SendRaw(client, cmd, selp) require.NoError(err) }) }) @@ -123,7 +127,10 @@ func TestSendRaw(t *testing.T) { apiServiceClient.EXPECT().SendAction(gomock.Any(), gomock.Any()).Return(nil, expectedErr) - err := SendRaw(client, selp) + cmd := NewActionCmd(client) + _, err := util.ExecuteCmd(cmd) + require.NoError(err) + err = SendRaw(client, cmd, selp) require.Contains(err.Error(), expectedErr.Error()) }) } From 5f16539e742fb1630ecd0b2c3a6ebfdefac9dd03 Mon Sep 17 00:00:00 2001 From: huof6890 <68298506@qq.com> Date: Thu, 14 Jul 2022 21:04:48 +0800 Subject: [PATCH 11/11] move testdata in function and directly use English usage --- ioctl/newcmd/action/action.go | 6 +---- ioctl/newcmd/action/action_test.go | 35 +++++++++++------------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/ioctl/newcmd/action/action.go b/ioctl/newcmd/action/action.go index b1696563ec..802aa14752 100644 --- a/ioctl/newcmd/action/action.go +++ b/ioctl/newcmd/action/action.go @@ -40,10 +40,6 @@ var ( config.English: "Manage actions of IoTeX blockchain", config.Chinese: "管理IoTex区块链的行为", // this translation } - _actionCmdUses = map[config.Language]string{ - config.English: "action", - config.Chinese: "action 行为", // this translation - } _infoWarn = map[config.Language]string{ config.English: "** This is an irreversible action!\n" + "Once an account is deleted, all the assets under this account may be lost!\n" + @@ -207,7 +203,7 @@ func selectTranslation(client ioctl.Client, trls map[config.Language]string) str // NewActionCmd represents the action command func NewActionCmd(client ioctl.Client) *cobra.Command { ac := &cobra.Command{ - Use: selectTranslation(client, _actionCmdUses), + Use: "action", Short: selectTranslation(client, _actionCmdShorts), } diff --git a/ioctl/newcmd/action/action_test.go b/ioctl/newcmd/action/action_test.go index 6daea7afa9..8a6bc1eb7c 100644 --- a/ioctl/newcmd/action/action_test.go +++ b/ioctl/newcmd/action/action_test.go @@ -21,27 +21,6 @@ import ( "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" ) -const ( - veryLightScryptN = 2 - veryLightScryptP = 1 -) - -var ( - testData = []struct { - endpoint string - insecure bool - }{ - { - endpoint: "111:222:333:444:5678", - insecure: false, - }, - { - endpoint: "", - insecure: true, - }, - } -) - func TestSigner(t *testing.T) { require := require.New(t) ctrl := gomock.NewController(t) @@ -73,7 +52,19 @@ func TestSendRaw(t *testing.T) { client.EXPECT().SelectTranslation(gomock.Any()).Return("mockTranslationString", config.English).Times(12) client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(7) - for _, test := range testData { + for _, test := range []struct { + endpoint string + insecure bool + }{ + { + endpoint: "111:222:333:444:5678", + insecure: false, + }, + { + endpoint: "", + insecure: true, + }, + } { callbackEndpoint := func(cb func(*string, string, string, string)) { cb(&test.endpoint, "endpoint", test.endpoint, "endpoint usage") }