package app import ( "bytes" "encoding/hex" "encoding/json" "fmt" "path/filepath" "strconv" "testing" "time" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/simapp/helpers" "github.com/cosmos/cosmos-sdk/snapshots" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/errors" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" "github.com/CosmWasm/wasmd/x/wasm" ) // DefaultConsensusParams defines the default Tendermint consensus params used in // WasmApp testing. var DefaultConsensusParams = &abci.ConsensusParams{ Block: &abci.BlockParams{ MaxBytes: 8000000, MaxGas: 1234000000, }, Evidence: &tmproto.EvidenceParams{ MaxAgeNumBlocks: 302400, MaxAgeDuration: 504 * time.Hour, // 3 weeks is the max duration MaxBytes: 10000, }, Validator: &tmproto.ValidatorParams{ PubKeyTypes: []string{ tmtypes.ABCIPubKeyTypeEd25519, }, }, } func setup(t testing.TB, withGenesis bool, invCheckPeriod uint, opts ...wasm.Option) (*WasmApp, GenesisState) { nodeHome := t.TempDir() snapshotDir := filepath.Join(nodeHome, "data", "snapshots") snapshotDB, err := sdk.NewLevelDB("metadata", snapshotDir) require.NoError(t, err) snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) require.NoError(t, err) baseAppOpts := []func(*bam.BaseApp){bam.SetSnapshotStore(snapshotStore), bam.SetSnapshotKeepRecent(2)} db := dbm.NewMemDB() app := NewWasmApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, nodeHome, invCheckPeriod, MakeEncodingConfig(), wasm.EnableAllProposals, EmptyBaseAppOptions{}, opts, baseAppOpts...) if withGenesis { return app, NewDefaultGenesisState() } return app, GenesisState{} } // Setup initializes a new WasmApp with DefaultNodeHome for integration tests func Setup(isCheckTx bool, opts ...wasm.Option) *WasmApp { db := dbm.NewMemDB() app := NewWasmApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, 5, MakeEncodingConfig(), wasm.EnableAllProposals, EmptyBaseAppOptions{}, opts) if !isCheckTx { genesisState := NewDefaultGenesisState() stateBytes, err := json.MarshalIndent(genesisState, "", " ") if err != nil { panic(err) } app.InitChain( abci.RequestInitChain{ Validators: []abci.ValidatorUpdate{}, ConsensusParams: DefaultConsensusParams, AppStateBytes: stateBytes, }, ) } return app } // SetupWithGenesisValSet initializes a new WasmApp with a validator set and genesis accounts // that also act as delegators. For simplicity, each validator is bonded with a delegation // of one consensus engine unit (10^6) in the default token of the WasmApp from first genesis // account. A Nop logger is set in WasmApp. func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, chainID string, opts []wasm.Option, balances ...banktypes.Balance) *WasmApp { app, genesisState := setup(t, true, 5, opts...) // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) genesisState[authtypes.ModuleName] = app.appCodec.MustMarshalJSON(authGenesis) validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) bondAmt := sdk.TokensFromConsensusPower(1, sdk.DefaultPowerReduction) for _, val := range valSet.Validators { pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) require.NoError(t, err) pkAny, err := codectypes.NewAnyWithValue(pk) require.NoError(t, err) validator := stakingtypes.Validator{ OperatorAddress: sdk.ValAddress(val.Address).String(), ConsensusPubkey: pkAny, Jailed: false, Status: stakingtypes.Bonded, Tokens: bondAmt, DelegatorShares: sdk.OneDec(), Description: stakingtypes.Description{}, UnbondingHeight: int64(0), UnbondingTime: time.Unix(0, 0).UTC(), Commission: stakingtypes.NewCommission(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), MinSelfDelegation: sdk.ZeroInt(), } validators = append(validators, validator) delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), sdk.OneDec())) } // set validators and delegations var stakingGenesis stakingtypes.GenesisState app.AppCodec().MustUnmarshalJSON(genesisState[stakingtypes.ModuleName], &stakingGenesis) bondDenom := stakingGenesis.Params.BondDenom // add bonded amount to bonded pool module account balances = append(balances, banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), Coins: sdk.Coins{sdk.NewCoin(bondDenom, bondAmt.Mul(sdk.NewInt(int64(len(valSet.Validators)))))}, }) // set validators and delegations stakingGenesis = *stakingtypes.NewGenesisState(stakingGenesis.Params, validators, delegations) genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(&stakingGenesis) // update total supply bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, sdk.NewCoins(), []banktypes.Metadata{}) genesisState[banktypes.ModuleName] = app.AppCodec().MustMarshalJSON(bankGenesis) stateBytes, err := json.MarshalIndent(genesisState, "", " ") require.NoError(t, err) // init chain will set the validator set and initialize the genesis accounts app.InitChain( abci.RequestInitChain{ ChainId: chainID, Validators: []abci.ValidatorUpdate{}, ConsensusParams: DefaultConsensusParams, AppStateBytes: stateBytes, }, ) // commit genesis changes app.Commit() app.BeginBlock( abci.RequestBeginBlock{ Header: tmproto.Header{ ChainID: chainID, Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash, ValidatorsHash: valSet.Hash(), NextValidatorsHash: valSet.Hash(), }, }, ) return app } // SetupWithEmptyStore setup a wasmd app instance with empty DB func SetupWithEmptyStore(t testing.TB) *WasmApp { app, _ := setup(t, false, 0) return app } type GenerateAccountStrategy func(int) []sdk.AccAddress // createRandomAccounts is a strategy used by addTestAddrs() in order to generated addresses in random order. func createRandomAccounts(accNum int) []sdk.AccAddress { testAddrs := make([]sdk.AccAddress, accNum) for i := 0; i < accNum; i++ { pk := ed25519.GenPrivKey().PubKey() testAddrs[i] = sdk.AccAddress(pk.Address()) } return testAddrs } // createIncrementalAccounts is a strategy used by addTestAddrs() in order to generated addresses in ascending order. func createIncrementalAccounts(accNum int) []sdk.AccAddress { addresses := make([]sdk.AccAddress, 0, accNum) var buffer bytes.Buffer // start at 100 so we can make up to 999 test addresses with valid test addresses for i := 100; i < (accNum + 100); i++ { numString := strconv.Itoa(i) buffer.WriteString("A58856F0FD53BF058B4909A21AEC019107BA6") // base address string buffer.WriteString(numString) // adding on final two digits to make addresses unique res, err := sdk.AccAddressFromHex(buffer.String()) if err != nil { panic(err) } bech := res.String() addr, err := TestAddr(buffer.String(), bech) if err != nil { panic(err) } addresses = append(addresses, addr) buffer.Reset() } return addresses } // AddTestAddrsFromPubKeys adds the addresses into the WasmApp providing only the public keys. func AddTestAddrsFromPubKeys(app *WasmApp, ctx sdk.Context, pubKeys []cryptotypes.PubKey, accAmt sdk.Int) { initCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), accAmt)) for _, pk := range pubKeys { initAccountWithCoins(app, ctx, sdk.AccAddress(pk.Address()), initCoins) } } // AddTestAddrs constructs and returns accNum amount of accounts with an // initial balance of accAmt in random order func AddTestAddrs(app *WasmApp, ctx sdk.Context, accNum int, accAmt sdk.Int) []sdk.AccAddress { return addTestAddrs(app, ctx, accNum, accAmt, createRandomAccounts) } // AddTestAddrs constructs and returns accNum amount of accounts with an // initial balance of accAmt in random order func AddTestAddrsIncremental(app *WasmApp, ctx sdk.Context, accNum int, accAmt sdk.Int) []sdk.AccAddress { return addTestAddrs(app, ctx, accNum, accAmt, createIncrementalAccounts) } func addTestAddrs(app *WasmApp, ctx sdk.Context, accNum int, accAmt sdk.Int, strategy GenerateAccountStrategy) []sdk.AccAddress { testAddrs := strategy(accNum) initCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), accAmt)) // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range testAddrs { initAccountWithCoins(app, ctx, addr, initCoins) } return testAddrs } func initAccountWithCoins(app *WasmApp, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) { err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins) if err != nil { panic(err) } err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, coins) if err != nil { panic(err) } } // ConvertAddrsToValAddrs converts the provided addresses to ValAddress. func ConvertAddrsToValAddrs(addrs []sdk.AccAddress) []sdk.ValAddress { valAddrs := make([]sdk.ValAddress, len(addrs)) for i, addr := range addrs { valAddrs[i] = sdk.ValAddress(addr) } return valAddrs } func TestAddr(addr string, bech string) (sdk.AccAddress, error) { res, err := sdk.AccAddressFromHex(addr) if err != nil { return nil, err } bechexpected := res.String() if bech != bechexpected { return nil, fmt.Errorf("bech encoding doesn't match reference") } bechres, err := sdk.AccAddressFromBech32(bech) if err != nil { return nil, err } if !bytes.Equal(bechres, res) { return nil, err } return res, nil } // CheckBalance checks the balance of an account. func CheckBalance(t *testing.T, app *WasmApp, addr sdk.AccAddress, balances sdk.Coins) { ctxCheck := app.BaseApp.NewContext(true, tmproto.Header{}) require.True(t, balances.IsEqual(app.BankKeeper.GetAllBalances(ctxCheck, addr))) } const DefaultGas = 1_500_000 // SignCheckDeliver checks a generated signed transaction and simulates a // block commitment with the given transaction. A test assertion is made using // the parameter 'expPass' against the result. A corresponding result is // returned. func SignCheckDeliver( t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, header tmproto.Header, msgs []sdk.Msg, chainID string, accNums, accSeqs []uint64, expSimPass, expPass bool, priv ...cryptotypes.PrivKey, ) (sdk.GasInfo, *sdk.Result, error) { tx, err := helpers.GenTx( txCfg, msgs, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, helpers.DefaultGenTxGas, chainID, accNums, accSeqs, priv..., ) require.NoError(t, err) txBytes, err := txCfg.TxEncoder()(tx) require.Nil(t, err) // Must simulate now as CheckTx doesn't run Msgs anymore _, res, err := app.Simulate(txBytes) if expSimPass { require.NoError(t, err) require.NotNil(t, res) } else { require.Error(t, err) require.Nil(t, res) } // Simulate a sending a transaction and committing a block app.BeginBlock(abci.RequestBeginBlock{Header: header}) gInfo, res, err := app.Deliver(txCfg.TxEncoder(), tx) if expPass { require.NoError(t, err) require.NotNil(t, res) } else { require.Error(t, err) require.Nil(t, res) } app.EndBlock(abci.RequestEndBlock{}) app.Commit() return gInfo, res, err } // SignAndDeliver signs and delivers a transaction. No simulation occurs as the // ibc testing package causes checkState and deliverState to diverge in block time. func SignAndDeliver( t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, header tmproto.Header, msgs []sdk.Msg, chainID string, accNums, accSeqs []uint64, priv ...cryptotypes.PrivKey, ) (sdk.GasInfo, *sdk.Result, error) { tx, err := helpers.GenTx( txCfg, msgs, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, 2*DefaultGas, chainID, accNums, accSeqs, priv..., ) require.NoError(t, err) // Simulate a sending a transaction and committing a block app.BeginBlock(abci.RequestBeginBlock{Header: header}) gInfo, res, err := app.Deliver(txCfg.TxEncoder(), tx) return gInfo, res, err } // GenSequenceOfTxs generates a set of signed transactions of messages, such // that they differ only by having the sequence numbers incremented between // every transaction. func GenSequenceOfTxs(txGen client.TxConfig, msgs []sdk.Msg, accNums []uint64, initSeqNums []uint64, numToGenerate int, priv ...cryptotypes.PrivKey) ([]sdk.Tx, error) { txs := make([]sdk.Tx, numToGenerate) var err error for i := 0; i < numToGenerate; i++ { txs[i], err = helpers.GenTx( txGen, msgs, sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)}, helpers.DefaultGenTxGas, "", accNums, initSeqNums, priv..., ) if err != nil { break } incrementAllSequenceNumbers(initSeqNums) } return txs, err } func incrementAllSequenceNumbers(initSeqNums []uint64) { for i := 0; i < len(initSeqNums); i++ { initSeqNums[i]++ } } // CreateTestPubKeys returns a total of numPubKeys public keys in ascending order. func CreateTestPubKeys(numPubKeys int) []cryptotypes.PubKey { publicKeys := make([]cryptotypes.PubKey, 0, numPubKeys) var buffer bytes.Buffer // start at 10 to avoid changing 1 to 01, 2 to 02, etc for i := 100; i < (numPubKeys + 100); i++ { numString := strconv.Itoa(i) buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") // base pubkey string buffer.WriteString(numString) // adding on final two digits to make pubkeys unique publicKeys = append(publicKeys, NewPubKeyFromHex(buffer.String())) buffer.Reset() } return publicKeys } // NewPubKeyFromHex returns a PubKey from a hex string. func NewPubKeyFromHex(pk string) (res cryptotypes.PubKey) { pkBytes, err := hex.DecodeString(pk) if err != nil { panic(err) } if len(pkBytes) != ed25519.PubKeySize { panic(errors.Wrap(errors.ErrInvalidPubKey, "invalid pubkey size")) } return &ed25519.PubKey{Key: pkBytes} } // EmptyBaseAppOptions is a stub implementing AppOptions type EmptyBaseAppOptions struct{} // Get implements AppOptions func (ao EmptyBaseAppOptions) Get(o string) interface{} { return nil } // FundAccount is a utility function that funds an account by minting and // sending the coins to the address. This should be used for testing purposes // only! // // Instead of using the mint module account, which has the // permission of minting, create a "faucet" account. (@fdymylja) func FundAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, amounts sdk.Coins) error { if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { return err } return bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, amounts) } // FundModuleAccount is a utility function that funds a module account by // minting and sending the coins to the address. This should be used for testing // purposes only! // // Instead of using the mint module account, which has the // permission of minting, create a "faucet" account. (@fdymylja) func FundModuleAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, recipientMod string, amounts sdk.Coins) error { if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { return err } return bankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, recipientMod, amounts) }