package cli_test import ( "context" "fmt" "io" "strings" "testing" abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" rpcclientmock "github.com/cometbft/cometbft/rpc/client/mock" "github.com/stretchr/testify/suite" "cosmossdk.io/core/address" "cosmossdk.io/math" "cosmossdk.io/x/bank" banktypes "cosmossdk.io/x/bank/types" "cosmossdk.io/x/gov" govtestutil "cosmossdk.io/x/gov/client/testutil" govtypes "cosmossdk.io/x/gov/types/v1beta1" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" addresscodec "github.com/cosmos/cosmos-sdk/codec/address" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil" "github.com/cosmos/cosmos-sdk/types/tx" "github.com/cosmos/cosmos-sdk/x/auth" authcli "github.com/cosmos/cosmos-sdk/x/auth/client/cli" authtestutil "github.com/cosmos/cosmos-sdk/x/auth/client/testutil" "github.com/cosmos/cosmos-sdk/x/genutil/client/cli" ) type CLITestSuite struct { suite.Suite kr keyring.Keyring encCfg testutilmod.TestEncodingConfig baseCtx client.Context clientCtx client.Context val sdk.AccAddress val1 sdk.AccAddress ac address.Codec } func TestCLITestSuite(t *testing.T) { suite.Run(t, new(CLITestSuite)) } func (s *CLITestSuite) SetupSuite() { s.encCfg = testutilmod.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}, bank.AppModule{}, gov.AppModule{}) s.kr = keyring.NewInMemory(s.encCfg.Codec) s.baseCtx = client.Context{}. WithKeyring(s.kr). WithTxConfig(s.encCfg.TxConfig). WithCodec(s.encCfg.Codec). WithClient(clitestutil.MockCometRPC{Client: rpcclientmock.Client{}}). WithAccountRetriever(client.MockAccountRetriever{}). WithOutput(io.Discard). WithChainID("test-chain"). WithAddressCodec(addresscodec.NewBech32Codec("cosmos")). WithValidatorAddressCodec(addresscodec.NewBech32Codec("cosmosvaloper")). WithConsensusAddressCodec(addresscodec.NewBech32Codec("cosmosvalcons")) ctxGen := func() client.Context { bz, _ := s.encCfg.Codec.Marshal(&sdk.TxResponse{}) c := clitestutil.NewMockCometRPC(abci.QueryResponse{ Value: bz, }) return s.baseCtx.WithClient(c) } s.clientCtx = ctxGen() kb := s.clientCtx.Keyring valAcc, _, err := kb.NewMnemonic("newAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) s.Require().NoError(err) s.val, err = valAcc.GetAddress() s.Require().NoError(err) pub, err := valAcc.GetPubKey() s.Require().NoError(err) account1, _, err := kb.NewMnemonic("newAccount1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) s.Require().NoError(err) s.val1, err = account1.GetAddress() s.Require().NoError(err) account2, _, err := kb.NewMnemonic("newAccount2", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) s.Require().NoError(err) pub1, err := account1.GetPubKey() s.Require().NoError(err) pub2, err := account2.GetPubKey() s.Require().NoError(err) // Create a dummy account for testing purpose _, _, err = kb.NewMnemonic("dummyAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) s.Require().NoError(err) // create a 2-3 multisig multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{pub, pub1, pub2}) _, err = kb.SaveMultisig("multi", multi) s.Require().NoError(err) s.ac = addresscodec.NewBech32Codec("cosmos") } func (s *CLITestSuite) TestCLIValidateSignatures() { sendTokens := sdk.NewCoins( sdk.NewCoin("testtoken", math.NewInt(10)), sdk.NewCoin("stake", math.NewInt(10))) res, err := s.createBankMsg(s.clientCtx, s.val, sendTokens, clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) // write unsigned tx to file unsignedTx := testutil.WriteToNewTempFile(s.T(), res.String()) defer unsignedTx.Close() res, err = authtestutil.TxSignExec(s.clientCtx, s.val, unsignedTx.Name()) s.Require().NoError(err) signedTx, err := s.clientCtx.TxConfig.TxJSONDecoder()(res.Bytes()) s.Require().NoError(err) signedTxFile := testutil.WriteToNewTempFile(s.T(), res.String()) defer signedTxFile.Close() txBuilder, err := s.clientCtx.TxConfig.WrapTxBuilder(signedTx) s.Require().NoError(err) _, err = authtestutil.TxValidateSignaturesExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) txBuilder.SetMemo("MODIFIED TX") bz, err := s.clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) s.Require().NoError(err) modifiedTxFile := testutil.WriteToNewTempFile(s.T(), string(bz)) defer modifiedTxFile.Close() _, err = authtestutil.TxValidateSignaturesExec(s.clientCtx, modifiedTxFile.Name()) s.Require().EqualError(err, "signatures validation failed") } func (s *CLITestSuite) TestCLISignBatch() { sendTokens := sdk.NewCoins( sdk.NewCoin("testtoken", math.NewInt(10)), sdk.NewCoin("stake", math.NewInt(10)), ) generatedStd, err := s.createBankMsg(s.clientCtx, s.val, sendTokens, clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) outputFile := testutil.WriteToNewTempFile(s.T(), strings.Repeat(generatedStd.String(), 3)) defer outputFile.Close() s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) // sign-batch file - offline is set but account-number and sequence are not _, err = authtestutil.TxSignBatchExec(s.clientCtx, s.val, outputFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagChainID, s.clientCtx.ChainID), "--offline") s.Require().EqualError(err, "required flag(s) \"account-number\", \"sequence\" not set") // sign-batch file - offline and sequence is set but account-number is not set _, err = authtestutil.TxSignBatchExec(s.clientCtx, s.val, outputFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagChainID, s.clientCtx.ChainID), fmt.Sprintf("--%s=%s", flags.FlagSequence, "1"), "--offline") s.Require().EqualError(err, "required flag(s) \"account-number\" not set") // sign-batch file - offline and account-number is set but sequence is not set _, err = authtestutil.TxSignBatchExec(s.clientCtx, s.val, outputFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagChainID, s.clientCtx.ChainID), fmt.Sprintf("--%s=%s", flags.FlagAccountNumber, "1"), "--offline") s.Require().EqualError(err, "required flag(s) \"sequence\" not set") } func (s *CLITestSuite) TestCLISignBatchTotalFees() { txCfg := s.clientCtx.TxConfig s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) testCases := []struct { name string args []string numTransactions int denom string }{ { "Offline batch-sign one transaction", []string{"--offline", "--account-number", "1", "--sequence", "1", "--append"}, 1, "stake", }, { "Offline batch-sign two transactions", []string{"--offline", "--account-number", "1", "--sequence", "1", "--append"}, 2, "stake", }, } for _, tc := range testCases { s.Run(tc.name, func() { // Create multiple transactions and write them to separate files sendTokens := sdk.NewCoin(tc.denom, sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)) expectedBatchedTotalFee := int64(0) txFiles := make([]string, tc.numTransactions) for i := 0; i < tc.numTransactions; i++ { tx, err := s.createBankMsg(s.clientCtx, s.val, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) txFile := testutil.WriteToNewTempFile(s.T(), tx.String()+"\n") txFiles[i] = txFile.Name() unsignedTx, err := txCfg.TxJSONDecoder()(tx.Bytes()) s.Require().NoError(err) txBuilder, err := txCfg.WrapTxBuilder(unsignedTx) s.Require().NoError(err) expectedBatchedTotalFee += txBuilder.GetTx().GetFee().AmountOf(tc.denom).Int64() err = txFile.Close() s.NoError(err) } // Test batch sign batchSignArgs := append([]string{fmt.Sprintf("--from=%s", s.val.String())}, append(txFiles, tc.args...)...) signedTx, err := clitestutil.ExecTestCLICmd(s.clientCtx, authcli.GetSignBatchCommand(), batchSignArgs) s.Require().NoError(err) signedFinalTx, err := txCfg.TxJSONDecoder()(signedTx.Bytes()) s.Require().NoError(err) txBuilder, err := txCfg.WrapTxBuilder(signedFinalTx) s.Require().NoError(err) finalTotalFee := txBuilder.GetTx().GetFee() s.Require().Equal(expectedBatchedTotalFee, finalTotalFee.AmountOf(tc.denom).Int64()) }) } } func (s *CLITestSuite) TestCLIQueryTxCmdByHash() { sendTokens := sdk.NewInt64Coin("stake", 10) // Send coins. out, err := s.createBankMsg( s.clientCtx, s.val, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{}, ) s.Require().NoError(err) var txRes sdk.TxResponse s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &txRes)) testCases := []struct { name string args []string expCmdOutput string }{ { "not enough args", []string{}, "", }, { "with invalid hash", []string{"somethinginvalid", fmt.Sprintf("--%s=json", flags.FlagOutput)}, `[somethinginvalid --output=json]`, }, { "with valid and not existing hash", []string{"C7E7D3A86A17AB3A321172239F3B61357937AF0F25D9FA4D2F4DCCAD9B0D7747", fmt.Sprintf("--%s=json", flags.FlagOutput)}, `[C7E7D3A86A17AB3A321172239F3B61357937AF0F25D9FA4D2F4DCCAD9B0D7747 --output=json`, }, { "happy case", []string{txRes.TxHash, fmt.Sprintf("--%s=json", flags.FlagOutput)}, fmt.Sprintf("%s --%s=json", txRes.TxHash, flags.FlagOutput), }, } for _, tc := range testCases { s.Run(tc.name, func() { cmd := authcli.QueryTxCmd() cmd.SetArgs(tc.args) if len(tc.args) != 0 { s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput) } }) } } func (s *CLITestSuite) TestCLIQueryTxCmdByEvents() { testCases := []struct { name string args []string expCmdOutput string }{ { "invalid --type", []string{ fmt.Sprintf("--type=%s", "foo"), "bar", fmt.Sprintf("--%s=json", flags.FlagOutput), }, "--type=foo bar --output=json", }, { "--type=acc_seq with no addr+seq", []string{ "--type=acc_seq", "", fmt.Sprintf("--%s=json", flags.FlagOutput), }, "--type=acc_seq --output=json", }, { "non-existing addr+seq combo", []string{ "--type=acc_seq", "foobar", fmt.Sprintf("--%s=json", flags.FlagOutput), }, "--type=acc_seq foobar --output=json", }, { "--type=signature with no signature", []string{ "--type=signature", "", fmt.Sprintf("--%s=json", flags.FlagOutput), }, "--type=signature --output=json", }, { "non-existing signatures", []string{ "--type=signature", "foo", fmt.Sprintf("--%s=json", flags.FlagOutput), }, "--type=signature foo --output=json", }, } for _, tc := range testCases { s.Run(tc.name, func() { cmd := authcli.QueryTxCmd() cmd.SetArgs(tc.args) if len(tc.args) != 0 { s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput) } }) } } func (s *CLITestSuite) TestCLIQueryTxsCmdByEvents() { testCases := []struct { name string args []string expCmdOutput string }{ { "fee event happy case", []string{ fmt.Sprintf( "--query=tx.fee='%s'", sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10))).String(), ), fmt.Sprintf("--%s=json", flags.FlagOutput), }, "", }, { "no matching fee event", []string{ fmt.Sprintf( "--query=tx.fee='%s'", sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(0))).String(), ), fmt.Sprintf("--%s=json", flags.FlagOutput), }, "", }, } for _, tc := range testCases { s.Run(tc.name, func() { cmd := authcli.QueryTxsByEventsCmd() if len(tc.args) != 0 { s.Require().Contains(fmt.Sprint(cmd), tc.expCmdOutput) } }) } } func (s *CLITestSuite) TestCLISendGenerateSignAndBroadcast() { sendTokens := sdk.NewCoin("stake", sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)) normalGeneratedTx, err := s.createBankMsg(s.clientCtx, s.val, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) txCfg := s.clientCtx.TxConfig normalGeneratedStdTx, err := txCfg.TxJSONDecoder()(normalGeneratedTx.Bytes()) s.Require().NoError(err) txBuilder, err := txCfg.WrapTxBuilder(normalGeneratedStdTx) s.Require().NoError(err) s.Require().Equal(txBuilder.GetTx().GetGas(), uint64(flags.DefaultGasLimit)) s.Require().Equal(len(txBuilder.GetTx().GetMsgs()), 1) sigs, err := txBuilder.GetTx().GetSignaturesV2() s.Require().NoError(err) s.Require().Equal(0, len(sigs)) // Test generate sendTx with --gas=$amount limitedGasGeneratedTx, err := s.createBankMsg(s.clientCtx, s.val, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{GenOnly: true, Gas: 100}, ) s.Require().NoError(err) limitedGasStdTx, err := txCfg.TxJSONDecoder()(limitedGasGeneratedTx.Bytes()) s.Require().NoError(err) txBuilder, err = txCfg.WrapTxBuilder(limitedGasStdTx) s.Require().NoError(err) s.Require().Equal(txBuilder.GetTx().GetGas(), uint64(100)) s.Require().Equal(len(txBuilder.GetTx().GetMsgs()), 1) sigs, err = txBuilder.GetTx().GetSignaturesV2() s.Require().NoError(err) s.Require().Equal(0, len(sigs)) // Test generate sendTx, estimate gas finalGeneratedTx, err := s.createBankMsg(s.clientCtx, s.val, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) finalStdTx, err := txCfg.TxJSONDecoder()(finalGeneratedTx.Bytes()) s.Require().NoError(err) txBuilder, err = txCfg.WrapTxBuilder(finalStdTx) s.Require().NoError(err) s.Require().Equal(uint64(flags.DefaultGasLimit), txBuilder.GetTx().GetGas()) s.Require().Equal(len(finalStdTx.GetMsgs()), 1) // Write the output to disk unsignedTxFile := testutil.WriteToNewTempFile(s.T(), finalGeneratedTx.String()) defer unsignedTxFile.Close() // Test validate-signatures res, err := authtestutil.TxValidateSignaturesExec(s.clientCtx, unsignedTxFile.Name()) s.Require().EqualError(err, "signatures validation failed") s.Require().Contains(res.String(), fmt.Sprintf("Signers:\n 0: %v\n\nSignatures:\n\n", s.val.String())) // Test sign // Does not work in offline mode _, err = authtestutil.TxSignExec(s.clientCtx, s.val, unsignedTxFile.Name(), "--offline") s.Require().EqualError(err, "required flag(s) \"account-number\", \"sequence\" not set") // But works offline if we set account number and sequence s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) _, err = authtestutil.TxSignExec(s.clientCtx, s.val, unsignedTxFile.Name(), "--offline", "--account-number", "1", "--sequence", "1") s.Require().NoError(err) // Sign transaction signedTx, err := authtestutil.TxSignExec(s.clientCtx, s.val, unsignedTxFile.Name()) s.Require().NoError(err) signedFinalTx, err := txCfg.TxJSONDecoder()(signedTx.Bytes()) s.Require().NoError(err) txBuilder, err = s.clientCtx.TxConfig.WrapTxBuilder(signedFinalTx) s.Require().NoError(err) s.Require().Equal(len(txBuilder.GetTx().GetMsgs()), 1) sigs, err = txBuilder.GetTx().GetSignaturesV2() s.Require().NoError(err) s.Require().Equal(1, len(sigs)) signers, err := txBuilder.GetTx().GetSigners() s.Require().NoError(err) s.Require().Equal([]byte(s.val), signers[0]) // Write the output to disk signedTxFile := testutil.WriteToNewTempFile(s.T(), signedTx.String()) defer signedTxFile.Close() // validate Signature res, err = authtestutil.TxValidateSignaturesExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) s.Require().True(strings.Contains(res.String(), "[OK]")) // Test broadcast // Does not work in offline mode _, err = authtestutil.TxBroadcastExec(s.clientCtx, signedTxFile.Name(), "--offline") s.Require().EqualError(err, "cannot broadcast tx during offline mode") // Broadcast correct transaction. s.clientCtx.BroadcastMode = flags.BroadcastSync _, err = authtestutil.TxBroadcastExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) } func (s *CLITestSuite) TestCLIMultisignInsufficientCosigners() { // Fetch account and a multisig info account1, err := s.clientCtx.Keyring.Key("newAccount1") s.Require().NoError(err) multisigRecord, err := s.clientCtx.Keyring.Key("multi") s.Require().NoError(err) addr, err := multisigRecord.GetAddress() s.Require().NoError(err) // Send coins from validator to multisig. _, err = s.createBankMsg( s.clientCtx, addr, sdk.NewCoins( sdk.NewInt64Coin("stake", 10), ), clitestutil.TestTxConfig{}, ) s.Require().NoError(err) msgSend := &banktypes.MsgSend{ FromAddress: addr.String(), ToAddress: s.val.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 5)), } multiGeneratedTx, err := clitestutil.SubmitTestTx( s.clientCtx, msgSend, addr, clitestutil.TestTxConfig{ GenOnly: true, }) s.Require().NoError(err) // Save tx to file multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String()) defer multiGeneratedTxFile.Close() // Multisign, sign with one signature s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) addr1, err := account1.GetAddress() s.Require().NoError(err) account1Signature, err := authtestutil.TxSignExec(s.clientCtx, addr1, multiGeneratedTxFile.Name(), "--multisig", addr.String()) s.Require().NoError(err) sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String()) defer sign1File.Close() multiSigWith1Signature, err := authtestutil.TxMultiSignExec(s.clientCtx, multisigRecord.Name, multiGeneratedTxFile.Name(), sign1File.Name()) s.Require().NoError(err) // Save tx to file multiSigWith1SignatureFile := testutil.WriteToNewTempFile(s.T(), multiSigWith1Signature.String()) defer multiSigWith1SignatureFile.Close() _, err = authtestutil.TxValidateSignaturesExec(s.clientCtx, multiSigWith1SignatureFile.Name()) s.Require().Error(err) } func (s *CLITestSuite) TestCLIEncode() { sendTokens := sdk.NewCoin("stake", sdk.TokensFromConsensusPower(10, sdk.DefaultPowerReduction)) normalGeneratedTx, err := s.createBankMsg( s.clientCtx, s.val, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{ GenOnly: true, Memo: "deadbeef", }, ) s.Require().NoError(err) savedTxFile := testutil.WriteToNewTempFile(s.T(), normalGeneratedTx.String()) defer savedTxFile.Close() // Encode encodeExec, err := authtestutil.TxEncodeExec(s.clientCtx, savedTxFile.Name()) s.Require().NoError(err) trimmedBase64 := strings.Trim(encodeExec.String(), "\"\n") // Check that the transaction decodes as expected decodedTx, err := authtestutil.TxDecodeExec(s.clientCtx, trimmedBase64) s.Require().NoError(err) txCfg := s.clientCtx.TxConfig theTx, err := txCfg.TxJSONDecoder()(decodedTx.Bytes()) s.Require().NoError(err) txBuilder, err := s.clientCtx.TxConfig.WrapTxBuilder(theTx) s.Require().NoError(err) s.Require().Equal("deadbeef", txBuilder.GetTx().GetMemo()) } func (s *CLITestSuite) TestCLIMultisignSortSignatures() { // Generate 2 accounts and a multisig. account1, err := s.clientCtx.Keyring.Key("newAccount1") s.Require().NoError(err) account2, err := s.clientCtx.Keyring.Key("newAccount2") s.Require().NoError(err) multisigRecord, err := s.clientCtx.Keyring.Key("multi") s.Require().NoError(err) // Generate dummy account which is not a part of multisig. dummyAcc, err := s.clientCtx.Keyring.Key("dummyAccount") s.Require().NoError(err) addr, err := multisigRecord.GetAddress() s.Require().NoError(err) msgSend := &banktypes.MsgSend{ FromAddress: addr.String(), ToAddress: s.val.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 5)), } // Generate multisig transaction. multiGeneratedTx, err := clitestutil.SubmitTestTx(s.clientCtx, msgSend, addr, clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) // Save tx to file multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String()) defer multiGeneratedTxFile.Close() // Sign with account1 addr1, err := account1.GetAddress() s.Require().NoError(err) s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) account1Signature, err := authtestutil.TxSignExec(s.clientCtx, addr1, multiGeneratedTxFile.Name(), "--multisig", addr.String()) s.Require().NoError(err) sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String()) defer sign1File.Close() // Sign with account2 addr2, err := account2.GetAddress() s.Require().NoError(err) account2Signature, err := authtestutil.TxSignExec(s.clientCtx, addr2, multiGeneratedTxFile.Name(), "--multisig", addr.String()) s.Require().NoError(err) sign2File := testutil.WriteToNewTempFile(s.T(), account2Signature.String()) defer sign2File.Close() // Sign with dummy account dummyAddr, err := dummyAcc.GetAddress() s.Require().NoError(err) _, err = authtestutil.TxSignExec(s.clientCtx, dummyAddr, multiGeneratedTxFile.Name(), "--multisig", addr.String()) s.Require().Error(err) s.Require().Contains(err.Error(), "signing key is not a part of multisig key") multiSigWith2Signatures, err := authtestutil.TxMultiSignExec(s.clientCtx, multisigRecord.Name, multiGeneratedTxFile.Name(), sign1File.Name(), sign2File.Name()) s.Require().NoError(err) // Write the output to disk signedTxFile := testutil.WriteToNewTempFile(s.T(), multiSigWith2Signatures.String()) defer signedTxFile.Close() _, err = authtestutil.TxValidateSignaturesExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) s.clientCtx.BroadcastMode = flags.BroadcastSync _, err = authtestutil.TxBroadcastExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) } func (s *CLITestSuite) TestSignWithMultisig() { // Generate a account for signing. account1, err := s.clientCtx.Keyring.Key("newAccount1") s.Require().NoError(err) addr1, err := account1.GetAddress() s.Require().NoError(err) // Create an address that is not in the keyring, will be used to simulate `--multisig` multisig := "cosmos1hd6fsrvnz6qkp87s3u86ludegq97agxsdkwzyh" _, err = s.ac.StringToBytes(multisig) s.Require().NoError(err) msgSend := &banktypes.MsgSend{ FromAddress: s.val.String(), ToAddress: s.val.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 5)), } // Generate a transaction for testing --multisig with an address not in the keyring. multisigTx, err := clitestutil.SubmitTestTx(s.clientCtx, msgSend, s.val, clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) // Save multi tx to file multiGeneratedTx2File := testutil.WriteToNewTempFile(s.T(), multisigTx.String()) defer multiGeneratedTx2File.Close() // Sign using multisig. We're signing a tx on behalf of the multisig address, // even though the tx signer is NOT the multisig address. This is fine though, // as the main point of this test is to test the `--multisig` flag with an address // that is not in the keyring. _, err = authtestutil.TxSignExec(s.clientCtx, addr1, multiGeneratedTx2File.Name(), "--multisig", multisig) s.Require().Contains(err.Error(), "error getting account from keybase") } func (s *CLITestSuite) TestCLIMultisign() { // Generate 2 accounts and a multisig. account1, err := s.clientCtx.Keyring.Key("newAccount1") s.Require().NoError(err) account2, err := s.clientCtx.Keyring.Key("newAccount2") s.Require().NoError(err) multisigRecord, err := s.clientCtx.Keyring.Key("multi") s.Require().NoError(err) addr, err := multisigRecord.GetAddress() s.Require().NoError(err) msgSend := &banktypes.MsgSend{ FromAddress: addr.String(), ToAddress: s.val.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 5)), } // Generate multisig transaction. multiGeneratedTx, err := clitestutil.SubmitTestTx(s.clientCtx, msgSend, addr, clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) // Save tx to file multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String()) defer multiGeneratedTxFile.Close() addr1, err := account1.GetAddress() s.Require().NoError(err) // Sign with account1 s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) account1Signature, err := authtestutil.TxSignExec(s.clientCtx, addr1, multiGeneratedTxFile.Name(), "--multisig", addr.String()) s.Require().NoError(err) sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String()) defer sign1File.Close() addr2, err := account2.GetAddress() s.Require().NoError(err) // Sign with account2 account2Signature, err := authtestutil.TxSignExec(s.clientCtx, addr2, multiGeneratedTxFile.Name(), "--multisig", addr.String()) s.Require().NoError(err) sign2File := testutil.WriteToNewTempFile(s.T(), account2Signature.String()) defer sign2File.Close() s.clientCtx.Offline = false multiSigWith2Signatures, err := authtestutil.TxMultiSignExec(s.clientCtx, multisigRecord.Name, multiGeneratedTxFile.Name(), sign1File.Name(), sign2File.Name()) s.Require().NoError(err) // Write the output to disk signedTxFile := testutil.WriteToNewTempFile(s.T(), multiSigWith2Signatures.String()) defer signedTxFile.Close() _, err = authtestutil.TxValidateSignaturesExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) s.clientCtx.BroadcastMode = flags.BroadcastSync _, err = authtestutil.TxBroadcastExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) } func (s *CLITestSuite) TestSignBatchMultisig() { // Fetch 2 accounts and a multisig. account1, err := s.clientCtx.Keyring.Key("newAccount1") s.Require().NoError(err) account2, err := s.clientCtx.Keyring.Key("newAccount2") s.Require().NoError(err) multisigRecord, err := s.clientCtx.Keyring.Key("multi") s.Require().NoError(err) addr, err := multisigRecord.GetAddress() s.Require().NoError(err) // Send coins from validator to multisig. sendTokens := sdk.NewInt64Coin("stake", 10) _, err = s.createBankMsg( s.clientCtx, addr, sdk.NewCoins(sendTokens), clitestutil.TestTxConfig{}, ) s.Require().NoError(err) msgSend := &banktypes.MsgSend{ FromAddress: addr.String(), ToAddress: s.val.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("stake", 5)), } generatedStd, err := clitestutil.SubmitTestTx(s.clientCtx, msgSend, addr, clitestutil.TestTxConfig{GenOnly: true}) s.Require().NoError(err) // Write the output to disk filename := testutil.WriteToNewTempFile(s.T(), strings.Repeat(generatedStd.String(), 1)) defer filename.Close() s.clientCtx.HomeDir = strings.Replace(s.clientCtx.HomeDir, "simd", "simcli", 1) addr1, err := account1.GetAddress() s.Require().NoError(err) // sign-batch file res, err := authtestutil.TxSignBatchExec(s.clientCtx, addr1, filename.Name(), fmt.Sprintf("--%s=%s", flags.FlagChainID, s.clientCtx.ChainID), "--multisig", addr.String(), "--signature-only") s.Require().NoError(err) s.Require().Equal(1, len(strings.Split(strings.Trim(res.String(), "\n"), "\n"))) // write sigs to file file1 := testutil.WriteToNewTempFile(s.T(), res.String()) defer file1.Close() addr2, err := account2.GetAddress() s.Require().NoError(err) // sign-batch file with account2 res, err = authtestutil.TxSignBatchExec(s.clientCtx, addr2, filename.Name(), fmt.Sprintf("--%s=%s", flags.FlagChainID, s.clientCtx.ChainID), "--multisig", addr.String(), "--signature-only") s.Require().NoError(err) s.Require().Equal(1, len(strings.Split(strings.Trim(res.String(), "\n"), "\n"))) // write sigs to file2 file2 := testutil.WriteToNewTempFile(s.T(), res.String()) defer file2.Close() _, err = authtestutil.TxMultiSignExec(s.clientCtx, multisigRecord.Name, filename.Name(), file1.Name(), file2.Name()) s.Require().NoError(err) } func (s *CLITestSuite) TestGetBroadcastCommandOfflineFlag() { cmd := authcli.GetBroadcastCommand() _ = testutil.ApplyMockIODiscardOutErr(cmd) cmd.SetArgs([]string{fmt.Sprintf("--%s=true", flags.FlagOffline), ""}) s.Require().EqualError(cmd.Execute(), "cannot broadcast tx during offline mode") } func (s *CLITestSuite) TestGetBroadcastCommandWithoutOfflineFlag() { txCfg := s.clientCtx.TxConfig clientCtx := client.Context{} clientCtx = clientCtx.WithTxConfig(txCfg) ctx := context.Background() ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) cmd := authcli.GetBroadcastCommand() _, out := testutil.ApplyMockIO(cmd) // Create new file with tx builder := txCfg.NewTxBuilder() builder.SetGasLimit(200000) err := builder.SetMsgs(banktypes.NewMsgSend("cosmos1cxlt8kznps92fwu3j6npahx4mjfutydyene2qw", "cosmos1cxlt8kznps92fwu3j6npahx4mjfutydyene2qw", sdk.Coins{sdk.NewInt64Coin("stake", 10000)})) s.Require().NoError(err) txContents, err := txCfg.TxJSONEncoder()(builder.GetTx()) s.Require().NoError(err) txFile := testutil.WriteToNewTempFile(s.T(), string(txContents)) defer txFile.Close() cmd.SetArgs([]string{txFile.Name()}) err = cmd.ExecuteContext(ctx) s.Require().Error(err) s.Require().Contains(err.Error(), "connect: connection refused") s.Require().Contains(out.String(), "connect: connection refused") } // TestTxWithoutPublicKey makes sure sending a proto tx message without the // public key doesn't cause any error in the RPC layer (broadcast). // See https://github.com/cosmos/cosmos-sdk/issues/7585 for more details. func (s *CLITestSuite) TestTxWithoutPublicKey() { txCfg := s.clientCtx.TxConfig valStr, err := s.ac.BytesToString(s.val) s.Require().NoError(err) // Create a txBuilder with an unsigned tx. txBuilder := txCfg.NewTxBuilder() msg := banktypes.NewMsgSend(valStr, valStr, sdk.NewCoins( sdk.NewCoin("Stake", math.NewInt(10)), )) err = txBuilder.SetMsgs(msg) s.Require().NoError(err) txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("Stake", math.NewInt(150)))) txBuilder.SetGasLimit(testdata.NewTestGasLimit()) // Create a file with the unsigned tx. txJSON, err := txCfg.TxJSONEncoder()(txBuilder.GetTx()) s.Require().NoError(err) unsignedTxFile := testutil.WriteToNewTempFile(s.T(), string(txJSON)) defer unsignedTxFile.Close() // Sign the file with the unsignedTx. signedTx, err := authtestutil.TxSignExec(s.clientCtx, s.val, unsignedTxFile.Name(), fmt.Sprintf("--%s=true", cli.FlagOverwrite)) s.Require().NoError(err) // Remove the signerInfo's `public_key` field manually from the signedTx. // Note: this method is only used for test purposes! In general, one should // use txBuilder and TxEncoder/TxDecoder to manipulate txs. var tx tx.Tx err = s.clientCtx.Codec.UnmarshalJSON(signedTx.Bytes(), &tx) s.Require().NoError(err) tx.AuthInfo.SignerInfos[0].PublicKey = nil // Re-encode the tx again, to another file. txJSON, err = s.clientCtx.Codec.MarshalJSON(&tx) s.Require().NoError(err) signedTxFile := testutil.WriteToNewTempFile(s.T(), string(txJSON)) defer signedTxFile.Close() s.Require().True(strings.Contains(string(txJSON), "\"public_key\":null")) // Broadcast tx, test that it shouldn't panic. s.clientCtx.BroadcastMode = flags.BroadcastSync out, err := authtestutil.TxBroadcastExec(s.clientCtx, signedTxFile.Name()) s.Require().NoError(err) var res sdk.TxResponse s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(out.Bytes(), &res)) s.Require().NotEqual(0, res.Code) } // TestSignWithMultiSignersAminoJSON tests the case where a transaction with 2 // messages which has to be signed with 2 different keys. Sign and append the // signatures using the CLI with Amino signing mode. Finally, send the // transaction to the blockchain. func (s *CLITestSuite) TestSignWithMultiSignersAminoJSON() { val0, val1 := s.val, s.val1 val0Coin := sdk.NewCoin("test1token", math.NewInt(10)) val1Coin := sdk.NewCoin("test2token", math.NewInt(10)) _, _, addr1 := testdata.KeyTestPubAddr() valStr, err := s.ac.BytesToString(val0) s.Require().NoError(err) val1Str, err := s.ac.BytesToString(val1) s.Require().NoError(err) addrStr, err := s.ac.BytesToString(addr1) s.Require().NoError(err) // Creating a tx with 2 msgs from 2 signers: val0 and val1. // The validators need to sign with SIGN_MODE_LEGACY_AMINO_JSON, // because DIRECT doesn't support multi signers via the CLI. // Since we use amino, we don't need to pre-populate signer_infos. txBuilder := s.clientCtx.TxConfig.NewTxBuilder() err = txBuilder.SetMsgs( banktypes.NewMsgSend(valStr, addrStr, sdk.NewCoins(val0Coin)), banktypes.NewMsgSend(val1Str, addrStr, sdk.NewCoins(val1Coin)), ) s.Require().NoError(err) txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("stake", math.NewInt(10)))) txBuilder.SetGasLimit(testdata.NewTestGasLimit() * 2) signers, err := txBuilder.GetTx().GetSigners() s.Require().NoError(err) s.Require().Equal([][]byte{val0, val1}, signers) // Write the unsigned tx into a file. txJSON, err := s.clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) s.Require().NoError(err) unsignedTxFile := testutil.WriteToNewTempFile(s.T(), string(txJSON)) defer unsignedTxFile.Close() // Let val0 sign first the file with the unsignedTx. signedByVal0, err := authtestutil.TxSignExec(s.clientCtx, val0, unsignedTxFile.Name(), "--overwrite", "--sign-mode=amino-json") s.Require().NoError(err) signedByVal0File := testutil.WriteToNewTempFile(s.T(), signedByVal0.String()) defer signedByVal0File.Close() // Then let val1 sign the file with signedByVal0. val1AccNum, val1Seq, err := s.clientCtx.AccountRetriever.GetAccountNumberSequence(s.clientCtx, val1) s.Require().NoError(err) signedTx, err := authtestutil.TxSignExec( s.clientCtx, val1, signedByVal0File.Name(), "--offline", fmt.Sprintf("--account-number=%d", val1AccNum), fmt.Sprintf("--sequence=%d", val1Seq), "--sign-mode=amino-json", ) s.Require().NoError(err) signedTxFile := testutil.WriteToNewTempFile(s.T(), signedTx.String()) defer signedTxFile.Close() res, err := authtestutil.TxBroadcastExec( s.clientCtx, signedTxFile.Name(), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), ) s.Require().NoError(err) var txRes sdk.TxResponse s.Require().NoError(s.clientCtx.Codec.UnmarshalJSON(res.Bytes(), &txRes)) s.Require().Equal(uint32(0), txRes.Code, txRes.RawLog) } func (s *CLITestSuite) TestAuxSigner() { s.T().Skip("re-enable this when we bring back sign mode aux client testing") val0Coin := sdk.NewCoin("testtoken", math.NewInt(10)) testCases := []struct { name string args []string expectErr bool }{ { "error with SIGN_MODE_DIRECT_AUX and --aux unset", []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), }, true, }, { "no error with SIGN_MDOE_DIRECT_AUX mode and generate-only set (ignores generate-only)", []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), }, false, }, { "no error with SIGN_MDOE_DIRECT_AUX mode and generate-only, tip flag set", []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), fmt.Sprintf("--%s=%s", flags.FlagTip, val0Coin.String()), }, false, }, } for _, tc := range testCases { s.Run(tc.name, func() { _, err := govtestutil.MsgSubmitLegacyProposal( s.clientCtx, s.val.String(), "test", "test desc", govtypes.ProposalTypeText, tc.args..., ) if tc.expectErr { s.Require().Error(err) } else { s.Require().NoError(err) } }) } } func (s *CLITestSuite) TestAuxToFeeWithTips() { // Skipping this test as it needs a simapp with the TipDecorator in post handler. s.T().Skip() require := s.Require() kb := s.clientCtx.Keyring acc, _, err := kb.NewMnemonic("tipperAccount", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) require.NoError(err) tipper, err := acc.GetAddress() require.NoError(err) tipperInitialBal := sdk.NewCoin("testtoken", math.NewInt(10000)) feePayer := s.val fee := sdk.NewCoin("stake", math.NewInt(1000)) tip := sdk.NewCoin("testtoken", math.NewInt(1000)) _, err = s.createBankMsg(s.clientCtx, tipper, sdk.NewCoins(tipperInitialBal), clitestutil.TestTxConfig{}) require.NoError(err) bal := s.getBalances(s.clientCtx, tipper, tip.Denom) require.True(bal.Equal(tipperInitialBal.Amount)) testCases := []struct { name string tipper sdk.AccAddress feePayer sdk.AccAddress tip sdk.Coin expectErrAux bool expectErrBroadCast bool errMsg string tipperArgs []string feePayerArgs []string }{ { name: "when --aux and --sign-mode = direct set: error", tipper: tipper, feePayer: feePayer, tip: tip, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect), fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=true", flags.FlagAux), }, expectErrAux: true, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, }, { name: "both tipper, fee payer uses AMINO: no error", tipper: tipper, feePayer: feePayer, tip: tip, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, }, { name: "tipper uses DIRECT_AUX, fee payer uses AMINO: no error", tipper: tipper, feePayer: feePayer, tip: tip, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, }, { name: "--tip flag unset: no error", tipper: tipper, feePayer: feePayer, tip: sdk.Coin{Denom: "testtoken", Amount: math.NewInt(0)}, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, }, { name: "legacy amino json: no error", tipper: tipper, feePayer: feePayer, tip: tip, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, }, { name: "tipper uses direct aux, fee payer uses direct: happy case", tipper: tipper, feePayer: feePayer, tip: tip, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, }, { name: "chain-id mismatch: error", tipper: tipper, feePayer: feePayer, tip: tip, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=true", flags.FlagAux), }, expectErrAux: false, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), fmt.Sprintf("--%s=%s", flags.FlagChainID, "foobar"), }, expectErrBroadCast: true, }, { name: "wrong denom in tip: error", tipper: tipper, feePayer: feePayer, tip: sdk.Coin{Denom: "testtoken", Amount: math.NewInt(0)}, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagTip, "1000wrongDenom"), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), fmt.Sprintf("--%s=%s", flags.FlagFees, fee.String()), }, errMsg: "insufficient funds", }, { name: "insufficient fees: error", tipper: tipper, feePayer: feePayer, tip: sdk.Coin{Denom: "testtoken", Amount: math.NewInt(0)}, tipperArgs: []string{ fmt.Sprintf("--%s=%s", flags.FlagTip, tip), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirectAux), fmt.Sprintf("--%s=true", flags.FlagAux), }, feePayerArgs: []string{ fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeDirect), fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFrom, feePayer), }, errMsg: "insufficient fees", }, } for _, tc := range testCases { s.Run(tc.name, func() { res, err := govtestutil.MsgSubmitLegacyProposal( s.clientCtx, tipper.String(), "test", "test desc", govtypes.ProposalTypeText, tc.tipperArgs..., ) if tc.expectErrAux { require.Error(err) } else { require.NoError(err) genTxFile := testutil.WriteToNewTempFile(s.T(), string(res.Bytes())) defer genTxFile.Close() switch { case tc.expectErrBroadCast: require.Error(err) case tc.errMsg != "": require.NoError(err) var txRes sdk.TxResponse require.NoError(s.clientCtx.Codec.UnmarshalJSON(res.Bytes(), &txRes)) require.Contains(txRes.RawLog, tc.errMsg) default: require.NoError(err) var txRes sdk.TxResponse require.NoError(s.clientCtx.Codec.UnmarshalJSON(res.Bytes(), &txRes)) require.Equal(uint32(0), txRes.Code) require.NotNil(int64(0), txRes.Height) bal = s.getBalances(s.clientCtx, tipper, tc.tip.Denom) tipperInitialBal = tipperInitialBal.Sub(tc.tip) require.True(bal.Equal(tipperInitialBal.Amount)) } } }) } } func (s *CLITestSuite) getBalances(clientCtx client.Context, addr sdk.AccAddress, denom string) math.Int { resp, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/bank/v1beta1/balances/%s/by_denom?denom=%s", s.baseCtx.NodeURI, addr.String(), denom)) s.Require().NoError(err) var balRes banktypes.QueryAllBalancesResponse err = clientCtx.Codec.UnmarshalJSON(resp, &balRes) s.Require().NoError(err) startTokens := balRes.Balances.AmountOf(denom) return startTokens } func (s *CLITestSuite) createBankMsg(clientCtx client.Context, toAddr sdk.AccAddress, amount sdk.Coins, cfg clitestutil.TestTxConfig) (testutil.BufferWriter, error) { msgSend := &banktypes.MsgSend{ FromAddress: s.val.String(), ToAddress: toAddr.String(), Amount: amount, } return clitestutil.SubmitTestTx(clientCtx, msgSend, s.val, cfg) }