package app import ( "encoding/json" "fmt" "math/rand" "os" "path/filepath" "testing" "time" dbm "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" tmjson "github.com/cometbft/cometbft/libs/json" "github.com/cometbft/cometbft/libs/log" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" tmtypes "github.com/cometbft/cometbft/types" "github.com/stretchr/testify/require" "cosmossdk.io/math" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/server" servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/snapshots" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" pruningtypes "github.com/cosmos/cosmos-sdk/store/pruning/types" "github.com/cosmos/cosmos-sdk/testutil/mock" "github.com/cosmos/cosmos-sdk/testutil/network" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module/testutil" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" ) // SetupOptions defines arguments that are passed into `WasmApp` constructor. type SetupOptions struct { Logger log.Logger DB *dbm.MemDB AppOpts servertypes.AppOptions WasmOpts []wasmkeeper.Option } func setup(tb testing.TB, chainID string, withGenesis bool, invCheckPeriod uint, opts ...wasmkeeper.Option) (*WasmApp, GenesisState) { tb.Helper() db := dbm.NewMemDB() nodeHome := tb.TempDir() snapshotDir := filepath.Join(nodeHome, "data", "snapshots") snapshotDB, err := dbm.NewDB("metadata", dbm.GoLevelDBBackend, snapshotDir) require.NoError(tb, err) tb.Cleanup(func() { snapshotDB.Close() }) snapshotStore, err := snapshots.NewStore(snapshotDB, snapshotDir) require.NoError(tb, err) appOptions := make(simtestutil.AppOptionsMap, 0) appOptions[flags.FlagHome] = nodeHome // ensure unique folder appOptions[server.FlagInvCheckPeriod] = invCheckPeriod app := NewWasmApp(log.NewNopLogger(), db, nil, true, appOptions, opts, bam.SetChainID(chainID), bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2})) if withGenesis { return app, NewDefaultGenesisState(app.AppCodec()) } return app, GenesisState{} } // NewWasmAppWithCustomOptions initializes a new WasmApp with custom options. func NewWasmAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptions) *WasmApp { t.Helper() privVal := mock.NewPV() pubKey, err := privVal.GetPubKey() require.NoError(t, err) // create validator set with single validator validator := tmtypes.NewValidator(pubKey, 1) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) // generate genesis account senderPrivKey := secp256k1.GenPrivKey() acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) balance := banktypes.Balance{ Address: acc.GetAddress().String(), Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), } app := NewWasmApp(options.Logger, options.DB, nil, true, options.AppOpts, options.WasmOpts) genesisState := NewDefaultGenesisState(app.appCodec) genesisState, err = GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, []authtypes.GenesisAccount{acc}, balance) require.NoError(t, err) if !isCheckTx { // init chain must be called to stop deliverState from being nil stateBytes, err := tmjson.MarshalIndent(genesisState, "", " ") require.NoError(t, err) // Initialize the chain app.InitChain( abci.RequestInitChain{ Validators: []abci.ValidatorUpdate{}, ConsensusParams: simtestutil.DefaultConsensusParams, AppStateBytes: stateBytes, }, ) } return app } // Setup initializes a new WasmApp. A Nop logger is set in WasmApp. func Setup(t *testing.T, opts ...wasmkeeper.Option) *WasmApp { t.Helper() privVal := mock.NewPV() pubKey, err := privVal.GetPubKey() require.NoError(t, err) // create validator set with single validator validator := tmtypes.NewValidator(pubKey, 1) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) // generate genesis account senderPrivKey := secp256k1.GenPrivKey() acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) balance := banktypes.Balance{ Address: acc.GetAddress().String(), Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), } chainID := "testing" app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, chainID, opts, balance) 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 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 []wasmkeeper.Option, balances ...banktypes.Balance) *WasmApp { t.Helper() app, genesisState := setup(t, chainID, true, 5, opts...) genesisState, err := GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...) require.NoError(t, err) stateBytes, err := json.MarshalIndent(genesisState, "", " ") require.NoError(t, err) // init chain will set the validator set and initialize the genesis accounts consensusParams := simtestutil.DefaultConsensusParams consensusParams.Block.MaxGas = 100 * simtestutil.DefaultGenTxGas app.InitChain( abci.RequestInitChain{ ChainId: chainID, Validators: []abci.ValidatorUpdate{}, ConsensusParams: consensusParams, AppStateBytes: stateBytes, }, ) // commit genesis changes app.Commit() votes := make([]abci.VoteInfo, len(valSet.Validators)) for i, v := range valSet.Validators { votes[i] = abci.VoteInfo{ Validator: abci.Validator{Address: v.Address, Power: v.VotingPower}, SignedLastBlock: true, } } app.BeginBlock(abci.RequestBeginBlock{ Header: tmproto.Header{ ChainID: chainID, Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash, Time: time.Now().UTC(), ValidatorsHash: valSet.Hash(), NextValidatorsHash: valSet.Hash(), }, LastCommitInfo: abci.CommitInfo{ Votes: votes, }, }) return app } // SetupWithEmptyStore set up a wasmd app instance with empty DB func SetupWithEmptyStore(tb testing.TB) *WasmApp { tb.Helper() app, _ := setup(tb, "testing", false, 0) return app } // GenesisStateWithSingleValidator initializes GenesisState with a single validator and genesis accounts // that also act as delegators. func GenesisStateWithSingleValidator(t *testing.T, app *WasmApp) GenesisState { t.Helper() privVal := mock.NewPV() pubKey, err := privVal.GetPubKey() require.NoError(t, err) // create validator set with single validator validator := tmtypes.NewValidator(pubKey, 1) valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) // generate genesis account senderPrivKey := secp256k1.GenPrivKey() acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) balances := []banktypes.Balance{ { Address: acc.GetAddress().String(), Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(100000000000000))), }, } genesisState := NewDefaultGenesisState(app.appCodec) genesisState, err = GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, []authtypes.GenesisAccount{acc}, balances...) require.NoError(t, err) return genesisState } // AddTestAddrsIncremental 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 math.Int) []sdk.AccAddress { return addTestAddrs(app, ctx, accNum, accAmt, simtestutil.CreateIncrementalAccounts) } func addTestAddrs(app *WasmApp, ctx sdk.Context, accNum int, accAmt math.Int, strategy simtestutil.GenerateAccountStrategy) []sdk.AccAddress { testAddrs := strategy(accNum) initCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), accAmt)) 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) } } // ModuleAccountAddrs provides a list of blocked module accounts from configuration in AppConfig // // Ported from WasmApp func ModuleAccountAddrs() map[string]bool { return BlockedAddresses() } var emptyWasmOptions []wasmkeeper.Option // NewTestNetworkFixture returns a new WasmApp AppConstructor for network simulation tests func NewTestNetworkFixture() network.TestFixture { dir, err := os.MkdirTemp("", "simapp") if err != nil { panic(fmt.Sprintf("failed creating temporary directory: %v", err)) } defer os.RemoveAll(dir) app := NewWasmApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(dir), emptyWasmOptions) appCtr := func(val network.ValidatorI) servertypes.Application { return NewWasmApp( val.GetCtx().Logger, dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(val.GetCtx().Config.RootDir), emptyWasmOptions, bam.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)), bam.SetMinGasPrices(val.GetAppConfig().MinGasPrices), bam.SetChainID(val.GetCtx().Viper.GetString(flags.FlagChainID)), ) } return network.TestFixture{ AppConstructor: appCtr, GenesisState: NewDefaultGenesisState(app.AppCodec()), EncodingConfig: testutil.TestEncodingConfig{ InterfaceRegistry: app.InterfaceRegistry(), Codec: app.AppCodec(), TxConfig: app.TxConfig(), Amino: app.LegacyAmino(), }, } } // SignAndDeliverWithoutCommit signs and delivers a transaction. No commit func SignAndDeliverWithoutCommit( t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, msgs []sdk.Msg, fees sdk.Coins, chainID string, accNums, accSeqs []uint64, priv ...cryptotypes.PrivKey, ) (sdk.GasInfo, *sdk.Result, error) { t.Helper() tx, err := simtestutil.GenSignedMockTx( rand.New(rand.NewSource(time.Now().UnixNano())), txCfg, msgs, fees, simtestutil.DefaultGenTxGas, 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.SimDeliver(txCfg.TxEncoder(), tx) // app.EndBlock(abci.RequestEndBlock{}) // app.Commit() return gInfo, res, err } // GenesisStateWithValSet returns a new genesis state with the validator set // copied from simtestutil with delegation not added to supply func GenesisStateWithValSet( codec codec.Codec, genesisState map[string]json.RawMessage, valSet *tmtypes.ValidatorSet, genAccs []authtypes.GenesisAccount, balances ...banktypes.Balance, ) (map[string]json.RawMessage, error) { // set genesis accounts authGenesis := authtypes.NewGenesisState(authtypes.DefaultParams(), genAccs) genesisState[authtypes.ModuleName] = codec.MustMarshalJSON(authGenesis) validators := make([]stakingtypes.Validator, 0, len(valSet.Validators)) delegations := make([]stakingtypes.Delegation, 0, len(valSet.Validators)) bondAmt := sdk.DefaultPowerReduction for _, val := range valSet.Validators { pk, err := cryptocodec.FromTmPubKeyInterface(val.PubKey) if err != nil { return nil, fmt.Errorf("failed to convert pubkey: %w", err) } pkAny, err := codectypes.NewAnyWithValue(pk) if err != nil { return nil, fmt.Errorf("failed to create new any: %w", err) } validator := stakingtypes.Validator{ OperatorAddress: sdk.ValAddress(val.Address).String(), ConsensusPubkey: pkAny, Jailed: false, Status: stakingtypes.Bonded, Tokens: bondAmt, DelegatorShares: math.LegacyOneDec(), Description: stakingtypes.Description{}, UnbondingHeight: int64(0), UnbondingTime: time.Unix(0, 0).UTC(), Commission: stakingtypes.NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()), MinSelfDelegation: math.ZeroInt(), } validators = append(validators, validator) delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress(), val.Address.Bytes(), math.LegacyOneDec())) } // set validators and delegations stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations) genesisState[stakingtypes.ModuleName] = codec.MustMarshalJSON(stakingGenesis) signingInfos := make([]slashingtypes.SigningInfo, len(valSet.Validators)) for i, val := range valSet.Validators { signingInfos[i] = slashingtypes.SigningInfo{ Address: sdk.ConsAddress(val.Address).String(), ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{}, } } slashingGenesis := slashingtypes.NewGenesisState(slashingtypes.DefaultParams(), signingInfos, nil) genesisState[slashingtypes.ModuleName] = codec.MustMarshalJSON(slashingGenesis) // add bonded amount to bonded pool module account balances = append(balances, banktypes.Balance{ Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(), Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators))))}, }) totalSupply := sdk.NewCoins() for _, b := range balances { // add genesis acc tokens to total supply totalSupply = totalSupply.Add(b.Coins...) } // update total supply bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{}) genesisState[banktypes.ModuleName] = codec.MustMarshalJSON(bankGenesis) println(string(genesisState[banktypes.ModuleName])) return genesisState, nil }