package clitest import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/require" cmn "github.com/tendermint/tendermint/libs/common" "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/tests" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" ) const ( denom = "stake" keyFoo = "foo" keyBar = "bar" fooDenom = "footoken" feeDenom = "feetoken" fee2Denom = "fee2token" keyBaz = "baz" keyFooBarBaz = "foobarbaz" ) var startCoins = sdk.Coins{ sdk.NewInt64Coin(feeDenom, 1000000), sdk.NewInt64Coin(fee2Denom, 1000000), sdk.NewInt64Coin(fooDenom, 1000), sdk.NewInt64Coin(denom, 150), } //___________________________________________________________________________________ // Fixtures // Fixtures is used to setup the testing environment type Fixtures struct { ChainID string RPCAddr string Port string GDHome string GCLIHome string P2PAddr string T *testing.T } // NewFixtures creates a new instance of Fixtures with many vars set func NewFixtures(t *testing.T) *Fixtures { tmpDir, err := ioutil.TempDir("", "gaia_integration_"+t.Name()+"_") require.NoError(t, err) servAddr, port, err := server.FreeTCPAddr() require.NoError(t, err) p2pAddr, _, err := server.FreeTCPAddr() require.NoError(t, err) return &Fixtures{ T: t, GDHome: filepath.Join(tmpDir, ".gaiad"), GCLIHome: filepath.Join(tmpDir, ".gaiacli"), RPCAddr: servAddr, P2PAddr: p2pAddr, Port: port, } } // InitFixtures is called at the beginning of a test // and initializes a chain with 1 validator func InitFixtures(t *testing.T) (f *Fixtures) { f = NewFixtures(t) // Reset test state f.UnsafeResetAll() // Ensure keystore has foo and bar keys f.KeysDelete(keyFoo) f.KeysDelete(keyBar) f.KeysDelete(keyBar) f.KeysDelete(keyFooBarBaz) f.KeysAdd(keyFoo) f.KeysAdd(keyBar) f.KeysAdd(keyBaz) f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf( "--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz)) // Ensure that CLI output is in JSON format f.CLIConfig("output", "json") // NOTE: GDInit sets the ChainID f.GDInit(keyFoo) f.CLIConfig("chain-id", f.ChainID) // Start an account with tokens f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) f.GenTx(keyFoo) f.CollectGenTxs() return } // Cleanup is meant to be run at the end of a test to clean up an remaining test state func (f *Fixtures) Cleanup(dirs ...string) { clean := append(dirs, f.GDHome, f.GCLIHome) for _, d := range clean { err := os.RemoveAll(d) require.NoError(f.T, err) } } // Flags returns the flags necessary for making most CLI calls func (f *Fixtures) Flags() string { return fmt.Sprintf("--home=%s --node=%s", f.GCLIHome, f.RPCAddr) } //___________________________________________________________________________________ // gaiad // UnsafeResetAll is gaiad unsafe-reset-all func (f *Fixtures) UnsafeResetAll(flags ...string) { cmd := fmt.Sprintf("gaiad --home=%s unsafe-reset-all", f.GDHome) executeWrite(f.T, addFlags(cmd, flags)) err := os.RemoveAll(filepath.Join(f.GDHome, "config", "gentx")) require.NoError(f.T, err) } // GDInit is gaiad init // NOTE: GDInit sets the ChainID for the Fixtures instance func (f *Fixtures) GDInit(moniker string, flags ...string) { cmd := fmt.Sprintf("gaiad init -o --moniker=%s --home=%s", moniker, f.GDHome) _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), app.DefaultKeyPass) var chainID string var initRes map[string]json.RawMessage err := json.Unmarshal([]byte(stderr), &initRes) require.NoError(f.T, err) err = json.Unmarshal(initRes["chain_id"], &chainID) require.NoError(f.T, err) f.ChainID = chainID } // AddGenesisAccount is gaiad add-genesis-account func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, flags ...string) { cmd := fmt.Sprintf("gaiad add-genesis-account %s %s --home=%s", address, coins, f.GDHome) executeWriteCheckErr(f.T, addFlags(cmd, flags)) } // GenTx is gaiad gentx func (f *Fixtures) GenTx(name string, flags ...string) { cmd := fmt.Sprintf("gaiad gentx --name=%s --home=%s --home-client=%s", name, f.GDHome, f.GCLIHome) executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // CollectGenTxs is gaiad collect-gentxs func (f *Fixtures) CollectGenTxs(flags ...string) { cmd := fmt.Sprintf("gaiad collect-gentxs --home=%s", f.GDHome) executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // GDStart runs gaiad start with the appropriate flags and returns a process func (f *Fixtures) GDStart(flags ...string) *tests.Process { cmd := fmt.Sprintf("gaiad start --home=%s --rpc.laddr=%v --p2p.laddr=%v", f.GDHome, f.RPCAddr, f.P2PAddr) proc := tests.GoExecuteTWithStdout(f.T, addFlags(cmd, flags)) tests.WaitForTMStart(f.Port) tests.WaitForNextNBlocksTM(1, f.Port) return proc } //___________________________________________________________________________________ // gaiacli keys // KeysDelete is gaiacli keys delete func (f *Fixtures) KeysDelete(name string, flags ...string) { cmd := fmt.Sprintf("gaiacli keys delete --home=%s %s", f.GCLIHome, name) executeWrite(f.T, addFlags(cmd, append(append(flags, "-y"), "-f"))) } // KeysAdd is gaiacli keys add func (f *Fixtures) KeysAdd(name string, flags ...string) { cmd := fmt.Sprintf("gaiacli keys add --home=%s %s", f.GCLIHome, name) executeWriteCheckErr(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // KeysShow is gaiacli keys show func (f *Fixtures) KeysShow(name string, flags ...string) keys.KeyOutput { cmd := fmt.Sprintf("gaiacli keys show --home=%s %s", f.GCLIHome, name) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var ko keys.KeyOutput err := keys.UnmarshalJSON([]byte(out), &ko) require.NoError(f.T, err) return ko } // KeyAddress returns the SDK account address from the key func (f *Fixtures) KeyAddress(name string) sdk.AccAddress { ko := f.KeysShow(name) accAddr, err := sdk.AccAddressFromBech32(ko.Address) require.NoError(f.T, err) return accAddr } //___________________________________________________________________________________ // gaiacli config // CLIConfig is gaiacli config func (f *Fixtures) CLIConfig(key, value string, flags ...string) { cmd := fmt.Sprintf("gaiacli config --home=%s %s %s", f.GCLIHome, key, value) executeWriteCheckErr(f.T, addFlags(cmd, flags)) } //___________________________________________________________________________________ // gaiacli tx send/sign/broadcast // TxSend is gaiacli tx send func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx send %v --amount=%s --to=%s --from=%s", f.Flags(), amount, to, from) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // TxSign is gaiacli tx sign func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx sign %v --name=%s %v", f.Flags(), signer, fileName) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // TxBroadcast is gaiacli tx sign func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx broadcast %v --json %v", f.Flags(), fileName) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // TxMultisign is gaiacli tx multisign func (f *Fixtures) TxMultisign(fileName, name string, signaturesFiles []string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx multisign %v %s %s %s", f.Flags(), fileName, name, strings.Join(signaturesFiles, " "), ) return executeWriteRetStdStreams(f.T, cmd) } //___________________________________________________________________________________ // gaiacli tx staking // TxStakingCreateValidator is gaiacli tx staking create-validator func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx staking create-validator %v --from=%s --pubkey=%s", f.Flags(), from, consPubKey) cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05") cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10") return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // TxStakingUnbond is gaiacli tx staking unbond func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool { cmd := fmt.Sprintf("gaiacli tx staking unbond %v --from=%s --validator=%s --shares-amount=%v", f.Flags(), from, validator, shares) return executeWrite(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } //___________________________________________________________________________________ // gaiacli tx gov // TxGovSubmitProposal is gaiacli tx gov submit-proposal func (f *Fixtures) TxGovSubmitProposal(from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx gov submit-proposal %v --from=%s --type=%s", f.Flags(), from, typ) cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // TxGovDeposit is gaiacli tx gov deposit func (f *Fixtures) TxGovDeposit(proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx gov deposit %d %s --from=%s %v", proposalID, amount, from, f.Flags()) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } // TxGovVote is gaiacli tx gov vote func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("gaiacli tx gov vote %d %s --from=%s %v", proposalID, option, from, f.Flags()) return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), app.DefaultKeyPass) } //___________________________________________________________________________________ // gaiacli query account // QueryAccount is gaiacli query account func (f *Fixtures) QueryAccount(address sdk.AccAddress, flags ...string) auth.BaseAccount { cmd := fmt.Sprintf("gaiacli query account %s %v", address, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var initRes map[string]json.RawMessage err := json.Unmarshal([]byte(out), &initRes) require.NoError(f.T, err, "out %v, err %v", out, err) value := initRes["value"] var acc auth.BaseAccount cdc := codec.New() codec.RegisterCrypto(cdc) err = cdc.UnmarshalJSON(value, &acc) require.NoError(f.T, err, "value %v, err %v", string(value), err) return acc } //___________________________________________________________________________________ // gaiacli query txs // QueryTxs is gaiacli query txs func (f *Fixtures) QueryTxs(page, limit int, tags ...string) []tx.Info { cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) out, _ := tests.ExecuteT(f.T, cmd, "") var txs []tx.Info cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &txs) require.NoError(f.T, err, "out %v\n, err %v", out, err) return txs } // QueryTxsInvalid query txs with wrong parameters and compare expected error func (f *Fixtures) QueryTxsInvalid(expectedErr error, page, limit int, tags ...string) { cmd := fmt.Sprintf("gaiacli query txs --page=%d --limit=%d --tags='%s' %v", page, limit, queryTags(tags), f.Flags()) _, err := tests.ExecuteT(f.T, cmd, "") require.EqualError(f.T, expectedErr, err) } //___________________________________________________________________________________ // gaiacli query staking // QueryStakingValidator is gaiacli query staking validator func (f *Fixtures) QueryStakingValidator(valAddr sdk.ValAddress, flags ...string) staking.Validator { cmd := fmt.Sprintf("gaiacli query staking validator %s %v", valAddr, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var validator staking.Validator cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &validator) require.NoError(f.T, err, "out %v\n, err %v", out, err) return validator } // QueryStakingUnbondingDelegationsFrom is gaiacli query staking unbonding-delegations-from func (f *Fixtures) QueryStakingUnbondingDelegationsFrom(valAddr sdk.ValAddress, flags ...string) []staking.UnbondingDelegation { cmd := fmt.Sprintf("gaiacli query staking unbonding-delegations-from %s %v", valAddr, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var ubds []staking.UnbondingDelegation cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &ubds) require.NoError(f.T, err, "out %v\n, err %v", out, err) return ubds } // QueryStakingDelegationsTo is gaiacli query staking delegations-to func (f *Fixtures) QueryStakingDelegationsTo(valAddr sdk.ValAddress, flags ...string) []staking.Delegation { cmd := fmt.Sprintf("gaiacli query staking delegations-to %s %v", valAddr, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var delegations []staking.Delegation cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &delegations) require.NoError(f.T, err, "out %v\n, err %v", out, err) return delegations } // QueryStakingPool is gaiacli query staking pool func (f *Fixtures) QueryStakingPool(flags ...string) staking.Pool { cmd := fmt.Sprintf("gaiacli query staking pool %v", f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var pool staking.Pool cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &pool) require.NoError(f.T, err, "out %v\n, err %v", out, err) return pool } // QueryStakingParameters is gaiacli query staking parameters func (f *Fixtures) QueryStakingParameters(flags ...string) staking.Params { cmd := fmt.Sprintf("gaiacli query staking params %v", f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var params staking.Params cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), ¶ms) require.NoError(f.T, err, "out %v\n, err %v", out, err) return params } //___________________________________________________________________________________ // gaiacli query gov // QueryGovParamDeposit is gaiacli query gov param deposit func (f *Fixtures) QueryGovParamDeposit() gov.DepositParams { cmd := fmt.Sprintf("gaiacli query gov param deposit %s", f.Flags()) out, _ := tests.ExecuteT(f.T, cmd, "") var depositParam gov.DepositParams cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &depositParam) require.NoError(f.T, err, "out %v\n, err %v", out, err) return depositParam } // QueryGovParamVoting is gaiacli query gov param voting func (f *Fixtures) QueryGovParamVoting() gov.VotingParams { cmd := fmt.Sprintf("gaiacli query gov param voting %s", f.Flags()) out, _ := tests.ExecuteT(f.T, cmd, "") var votingParam gov.VotingParams cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &votingParam) require.NoError(f.T, err, "out %v\n, err %v", out, err) return votingParam } // QueryGovParamTallying is gaiacli query gov param tallying func (f *Fixtures) QueryGovParamTallying() gov.TallyParams { cmd := fmt.Sprintf("gaiacli query gov param tallying %s", f.Flags()) out, _ := tests.ExecuteT(f.T, cmd, "") var tallyingParam gov.TallyParams cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &tallyingParam) require.NoError(f.T, err, "out %v\n, err %v", out, err) return tallyingParam } // QueryGovProposals is gaiacli query gov proposals func (f *Fixtures) QueryGovProposals(flags ...string) gov.Proposals { cmd := fmt.Sprintf("gaiacli query gov proposals %v", f.Flags()) stdout, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), "") if strings.Contains(stderr, "No matching proposals found") { return gov.Proposals{} } require.Empty(f.T, stderr) var out gov.Proposals cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(stdout), &out) require.NoError(f.T, err) return out } // QueryGovProposal is gaiacli query gov proposal func (f *Fixtures) QueryGovProposal(proposalID int, flags ...string) gov.Proposal { cmd := fmt.Sprintf("gaiacli query gov proposal %d %v", proposalID, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var proposal gov.Proposal cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &proposal) require.NoError(f.T, err, "out %v\n, err %v", out, err) return proposal } // QueryGovVote is gaiacli query gov vote func (f *Fixtures) QueryGovVote(proposalID int, voter sdk.AccAddress, flags ...string) gov.Vote { cmd := fmt.Sprintf("gaiacli query gov vote %d %s %v", proposalID, voter, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var vote gov.Vote cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &vote) require.NoError(f.T, err, "out %v\n, err %v", out, err) return vote } // QueryGovVotes is gaiacli query gov votes func (f *Fixtures) QueryGovVotes(proposalID int, flags ...string) []gov.Vote { cmd := fmt.Sprintf("gaiacli query gov votes %d %v", proposalID, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var votes []gov.Vote cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &votes) require.NoError(f.T, err, "out %v\n, err %v", out, err) return votes } // QueryGovDeposit is gaiacli query gov deposit func (f *Fixtures) QueryGovDeposit(proposalID int, depositor sdk.AccAddress, flags ...string) gov.Deposit { cmd := fmt.Sprintf("gaiacli query gov deposit %d %s %v", proposalID, depositor, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var deposit gov.Deposit cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &deposit) require.NoError(f.T, err, "out %v\n, err %v", out, err) return deposit } // QueryGovDeposits is gaiacli query gov deposits func (f *Fixtures) QueryGovDeposits(propsalID int, flags ...string) []gov.Deposit { cmd := fmt.Sprintf("gaiacli query gov deposits %d %v", propsalID, f.Flags()) out, _ := tests.ExecuteT(f.T, addFlags(cmd, flags), "") var deposits []gov.Deposit cdc := app.MakeCodec() err := cdc.UnmarshalJSON([]byte(out), &deposits) require.NoError(f.T, err, "out %v\n, err %v", out, err) return deposits } //___________________________________________________________________________________ // query slashing // QuerySlashingParams is gaiacli query slashing params func (f *Fixtures) QuerySlashingParams() slashing.Params { cmd := fmt.Sprintf("gaiacli query slashing params %s", f.Flags()) res, errStr := tests.ExecuteT(f.T, cmd, "") require.Empty(f.T, errStr) cdc := app.MakeCodec() var params slashing.Params err := cdc.UnmarshalJSON([]byte(res), ¶ms) require.NoError(f.T, err) return params } //___________________________________________________________________________________ // executors func executeWriteCheckErr(t *testing.T, cmdStr string, writes ...string) { require.True(t, executeWrite(t, cmdStr, writes...)) } func executeWrite(t *testing.T, cmdStr string, writes ...string) (exitSuccess bool) { exitSuccess, _, _ = executeWriteRetStdStreams(t, cmdStr, writes...) return } func executeWriteRetStdStreams(t *testing.T, cmdStr string, writes ...string) (bool, string, string) { proc := tests.GoExecuteT(t, cmdStr) // Enables use of interactive commands for _, write := range writes { _, err := proc.StdinPipe.Write([]byte(write + "\n")) require.NoError(t, err) } // Read both stdout and stderr from the process stdout, stderr, err := proc.ReadAll() if err != nil { fmt.Println("Err on proc.ReadAll()", err, cmdStr) } // Log output. if len(stdout) > 0 { t.Log("Stdout:", cmn.Green(string(stdout))) } if len(stderr) > 0 { t.Log("Stderr:", cmn.Red(string(stderr))) } // Wait for process to exit proc.Wait() // Return succes, stdout, stderr return proc.ExitState.Success(), string(stdout), string(stderr) } //___________________________________________________________________________________ // utils func addFlags(cmd string, flags []string) string { for _, f := range flags { cmd += " " + f } return strings.TrimSpace(cmd) } func queryTags(tags []string) (out string) { for _, tag := range tags { out += tag + "&" } return strings.TrimSuffix(out, "&") } func writeToNewTempFile(t *testing.T, s string) *os.File { fp, err := ioutil.TempFile(os.TempDir(), "cosmos_cli_test_") require.Nil(t, err) _, err = fp.WriteString(s) require.Nil(t, err) return fp } func marshalStdTx(t *testing.T, stdTx auth.StdTx) []byte { cdc := app.MakeCodec() bz, err := cdc.MarshalBinaryBare(stdTx) require.NoError(t, err) return bz } func unmarshalStdTx(t *testing.T, s string) (stdTx auth.StdTx) { cdc := app.MakeCodec() require.Nil(t, cdc.UnmarshalJSON([]byte(s), &stdTx)) return }