package keeper_test import ( "context" "encoding/hex" "sort" "sync/atomic" "testing" "github.com/golang/mock/gomock" "github.com/stretchr/testify/suite" "pgregory.net/rapid" "cosmossdk.io/core/appmodule" "cosmossdk.io/core/header" coretesting "cosmossdk.io/core/testing" storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/auth" authcodec "cosmossdk.io/x/auth/codec" "cosmossdk.io/x/auth/keeper" authtestutil "cosmossdk.io/x/auth/testutil" "cosmossdk.io/x/auth/types" "github.com/cosmos/cosmos-sdk/baseapp" codectestutil "github.com/cosmos/cosmos-sdk/codec/testutil" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" "github.com/cosmos/cosmos-sdk/runtime" "github.com/cosmos/cosmos-sdk/testutil" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" ) type DeterministicTestSuite struct { suite.Suite accountNumberLanes uint64 key *storetypes.KVStoreKey environment appmodule.Environment ctx sdk.Context queryClient types.QueryClient accountKeeper keeper.AccountKeeper acctsModKeeper *authtestutil.MockAccountsModKeeper encCfg moduletestutil.TestEncodingConfig maccPerms map[string][]string } var ( addr = sdk.MustAccAddressFromBech32("cosmos1j364pjm8jkxxmujj0vp2xjg0y7w8tyveuamfm6") pub, _ = hex.DecodeString("01090C02812F010C25200ED40E004105160196E801F70005070EA21603FF06001E") permissions = []string{"burner", "minter", "staking", "random"} ) func TestDeterministicTestSuite(t *testing.T) { suite.Run(t, new(DeterministicTestSuite)) } func (suite *DeterministicTestSuite) SetupTest() { suite.encCfg = moduletestutil.MakeTestEncodingConfig(codectestutil.CodecOptions{}, auth.AppModule{}) suite.Require() key := storetypes.NewKVStoreKey(types.StoreKey) storeService := runtime.NewKVStoreService(key) env := runtime.NewEnvironment(storeService, coretesting.NewNopLogger()) testCtx := testutil.DefaultContextWithDB(suite.T(), key, storetypes.NewTransientStoreKey("transient_test")) suite.ctx = testCtx.Ctx.WithHeaderInfo(header.Info{}) // gomock initializations ctrl := gomock.NewController(suite.T()) acctsModKeeper := authtestutil.NewMockAccountsModKeeper(ctrl) suite.acctsModKeeper = acctsModKeeper accNum := uint64(0) suite.acctsModKeeper.EXPECT().NextAccountNumber(gomock.Any()).AnyTimes().DoAndReturn(func(ctx context.Context) (uint64, error) { currNum := accNum accNum++ return currNum, nil }) maccPerms := map[string][]string{ "fee_collector": nil, "mint": {"minter"}, "bonded_tokens_pool": {"burner", "staking"}, "not_bonded_tokens_pool": {"burner", "staking"}, multiPerm: {"burner", "minter", "staking"}, randomPerm: {"random"}, } suite.accountKeeper = keeper.NewAccountKeeper( env, suite.encCfg.Codec, types.ProtoBaseAccount, suite.acctsModKeeper, maccPerms, authcodec.NewBech32Codec("cosmos"), "cosmos", types.NewModuleAddress("gov").String(), ) queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.encCfg.InterfaceRegistry) types.RegisterQueryServer(queryHelper, keeper.NewQueryServer(suite.accountKeeper)) suite.queryClient = types.NewQueryClient(queryHelper) suite.key = key suite.environment = env suite.maccPerms = maccPerms suite.accountNumberLanes = 1 } // createAndSetAccounts creates a random account and sets to the keeper store. func (suite *DeterministicTestSuite) createAndSetAccounts(t *rapid.T, count int) []sdk.AccountI { accs := make([]sdk.AccountI, 0, count) // We need all generated account-numbers unique accNums := rapid.SliceOfNDistinct(rapid.Uint64(), count, count, func(i uint64) uint64 { return i }).Draw(t, "acc-nums") // then we change account numbers in such a way that there cannot be accounts with the same account number lane := atomic.AddUint64(&suite.accountNumberLanes, 1) for i := range accNums { accNums[i] += lane * 1000 } for i := 0; i < count; i++ { pub := pubkeyGenerator(t).Draw(t, "pubkey") addr := sdk.AccAddress(pub.Address()) accNum := accNums[i] seq := rapid.Uint64().Draw(t, "sequence") acc1 := types.NewBaseAccount(addr, &pub, accNum, seq) suite.accountKeeper.SetAccount(suite.ctx, acc1) accs = append(accs, acc1) } return accs } func (suite *DeterministicTestSuite) TestGRPCQueryAccount() { rapid.Check(suite.T(), func(t *rapid.T) { accs := suite.createAndSetAccounts(t, 1) req := &types.QueryAccountRequest{Address: accs[0].GetAddress().String()} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.Account, 0, true) }) // Regression tests accNum := uint64(10087) seq := uint64(98) acc1 := types.NewBaseAccount(addr, &secp256k1.PubKey{Key: pub}, accNum, seq) suite.accountKeeper.SetAccount(suite.ctx, acc1) req := &types.QueryAccountRequest{Address: acc1.GetAddress().String()} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.Account, 1543, false) } // pubkeyGenerator creates and returns a random pubkey generator using rapid. func pubkeyGenerator(_ *rapid.T) *rapid.Generator[secp256k1.PubKey] { return rapid.Custom(func(t *rapid.T) secp256k1.PubKey { pkBz := rapid.SliceOfN(rapid.Byte(), 33, 33).Draw(t, "hex") return secp256k1.PubKey{Key: pkBz} }) } func (suite *DeterministicTestSuite) TestGRPCQueryAccounts() { rapid.Check(suite.T(), func(t *rapid.T) { numAccs := rapid.IntRange(1, 10).Draw(t, "accounts") accs := suite.createAndSetAccounts(t, numAccs) req := &types.QueryAccountsRequest{Pagination: testdata.PaginationGenerator(t, uint64(numAccs)).Draw(t, "accounts")} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.Accounts, 0, true) for i := 0; i < numAccs; i++ { suite.accountKeeper.RemoveAccount(suite.ctx, accs[i]) } }) // Regression test addr1, err := suite.accountKeeper.AddressCodec().StringToBytes("cosmos1892yr6fzlj7ud0kfkah2ctrav3a4p4n060ze8f") suite.Require().NoError(err) pub1, err := hex.DecodeString("D1002E1B019000010BB7034500E71F011F1CA90D5B000E134BFB0F3603030D0303") suite.Require().NoError(err) accNum1 := uint64(107) seq1 := uint64(10001) accNum2 := uint64(100) seq2 := uint64(10) acc1 := types.NewBaseAccount(addr1, &secp256k1.PubKey{Key: pub1}, accNum1, seq1) acc2 := types.NewBaseAccount(addr, &secp256k1.PubKey{Key: pub}, accNum2, seq2) suite.accountKeeper.SetAccount(suite.ctx, acc1) suite.accountKeeper.SetAccount(suite.ctx, acc2) req := &types.QueryAccountsRequest{} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.Accounts, 1716, false) } func (suite *DeterministicTestSuite) TestGRPCQueryAccountAddressByID() { rapid.Check(suite.T(), func(t *rapid.T) { accs := suite.createAndSetAccounts(t, 1) req := &types.QueryAccountAddressByIDRequest{AccountId: accs[0].GetAccountNumber()} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.AccountAddressByID, 0, true) }) // Regression test accNum := uint64(10087) seq := uint64(0) acc1 := types.NewBaseAccount(addr, &secp256k1.PubKey{Key: pub}, accNum, seq) suite.accountKeeper.SetAccount(suite.ctx, acc1) req := &types.QueryAccountAddressByIDRequest{AccountId: accNum} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.AccountAddressByID, 1123, false) } func (suite *DeterministicTestSuite) TestGRPCQueryParameters() { rapid.Check(suite.T(), func(t *rapid.T) { params := types.NewParams( rapid.Uint64Min(1).Draw(t, "max-memo-characters"), rapid.Uint64Min(1).Draw(t, "tx-sig-limit"), rapid.Uint64Min(1).Draw(t, "tx-size-cost-per-byte"), rapid.Uint64Min(1).Draw(t, "sig-verify-cost-ed25519"), rapid.Uint64Min(1).Draw(t, "sig-verify-cost-Secp256k1"), ) err := suite.accountKeeper.Params.Set(suite.ctx, params) suite.Require().NoError(err) req := &types.QueryParamsRequest{} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.Params, 0, true) }) // Regression test params := types.NewParams(15, 167, 100, 1, 21457) err := suite.accountKeeper.Params.Set(suite.ctx, params) suite.Require().NoError(err) req := &types.QueryParamsRequest{} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.Params, 1042, false) } func (suite *DeterministicTestSuite) TestGRPCQueryAccountInfo() { rapid.Check(suite.T(), func(t *rapid.T) { accs := suite.createAndSetAccounts(t, 1) suite.Require().Len(accs, 1) req := &types.QueryAccountInfoRequest{Address: accs[0].GetAddress().String()} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.AccountInfo, 0, true) }) // Regression test accNum := uint64(10087) seq := uint64(10) acc := types.NewBaseAccount(addr, &secp256k1.PubKey{Key: pub}, accNum, seq) suite.accountKeeper.SetAccount(suite.ctx, acc) req := &types.QueryAccountInfoRequest{Address: acc.GetAddress().String()} testdata.DeterministicIterations(suite.T(), suite.ctx, req, suite.queryClient.AccountInfo, 1543, false) } func (suite *DeterministicTestSuite) createAndReturnQueryClient(ak keeper.AccountKeeper) types.QueryClient { queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.encCfg.InterfaceRegistry) types.RegisterQueryServer(queryHelper, keeper.NewQueryServer(ak)) return types.NewQueryClient(queryHelper) } func (suite *DeterministicTestSuite) setModuleAccounts( ctx sdk.Context, ak keeper.AccountKeeper, maccs []string, ) []sdk.AccountI { sort.Strings(maccs) moduleAccounts := make([]sdk.AccountI, 0, len(maccs)) for _, m := range maccs { acc, _ := ak.GetModuleAccountAndPermissions(ctx, m) acc1, ok := acc.(sdk.AccountI) suite.Require().True(ok) moduleAccounts = append(moduleAccounts, acc1) } return moduleAccounts } func (suite *DeterministicTestSuite) TestGRPCQueryModuleAccounts() { rapid.Check(suite.T(), func(t *rapid.T) { maccsCount := rapid.IntRange(1, 10).Draw(t, "accounts") maccs := make([]string, maccsCount) for i := 0; i < maccsCount; i++ { maccs[i] = rapid.StringMatching(`[a-z]{5,}`).Draw(t, "module-name") } maccPerms := make(map[string][]string) for i := 0; i < maccsCount; i++ { mPerms := make([]string, 0, 4) for _, permission := range permissions { if rapid.Bool().Draw(t, "permissions") { mPerms = append(mPerms, permission) } } if len(mPerms) == 0 { num := rapid.IntRange(0, 3).Draw(t, "num") mPerms = append(mPerms, permissions[num]) } maccPerms[maccs[i]] = mPerms } ak := keeper.NewAccountKeeper( suite.environment, suite.encCfg.Codec, types.ProtoBaseAccount, suite.acctsModKeeper, maccPerms, authcodec.NewBech32Codec("cosmos"), "cosmos", types.NewModuleAddress("gov").String(), ) suite.setModuleAccounts(suite.ctx, ak, maccs) queryClient := suite.createAndReturnQueryClient(ak) req := &types.QueryModuleAccountsRequest{} testdata.DeterministicIterations(suite.T(), suite.ctx, req, queryClient.ModuleAccounts, 0, true) }) maccs := make([]string, 0, len(suite.maccPerms)) for k := range suite.maccPerms { maccs = append(maccs, k) } suite.setModuleAccounts(suite.ctx, suite.accountKeeper, maccs) queryClient := suite.createAndReturnQueryClient(suite.accountKeeper) req := &types.QueryModuleAccountsRequest{} testdata.DeterministicIterations(suite.T(), suite.ctx, req, queryClient.ModuleAccounts, 8565, false) } func (suite *DeterministicTestSuite) TestGRPCQueryModuleAccountByName() { rapid.Check(suite.T(), func(t *rapid.T) { mName := rapid.StringMatching(`[a-z]{5,}`).Draw(t, "module-name") maccPerms := make(map[string][]string) mPerms := make([]string, 0, 4) for _, permission := range permissions { if rapid.Bool().Draw(t, "permissions") { mPerms = append(mPerms, permission) } } if len(mPerms) == 0 { num := rapid.IntRange(0, 3).Draw(t, "num") mPerms = append(mPerms, permissions[num]) } maccPerms[mName] = mPerms ak := keeper.NewAccountKeeper( suite.environment, suite.encCfg.Codec, types.ProtoBaseAccount, suite.acctsModKeeper, maccPerms, authcodec.NewBech32Codec("cosmos"), "cosmos", types.NewModuleAddress("gov").String(), ) suite.setModuleAccounts(suite.ctx, ak, []string{mName}) queryClient := suite.createAndReturnQueryClient(ak) req := &types.QueryModuleAccountByNameRequest{Name: mName} testdata.DeterministicIterations(suite.T(), suite.ctx, req, queryClient.ModuleAccountByName, 0, true) }) maccs := make([]string, 0, len(suite.maccPerms)) for k := range suite.maccPerms { maccs = append(maccs, k) } suite.setModuleAccounts(suite.ctx, suite.accountKeeper, maccs) queryClient := suite.createAndReturnQueryClient(suite.accountKeeper) req := &types.QueryModuleAccountByNameRequest{Name: "mint"} testdata.DeterministicIterations(suite.T(), suite.ctx, req, queryClient.ModuleAccountByName, 1372, false) }