Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tests): add operators simulation tests #190

Merged
merged 3 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func appModules(

// MilkyWay modules
services.NewAppModule(appCodec, app.ServicesKeeper, app.AccountKeeper, app.BankKeeper),
operators.NewAppModule(appCodec, app.OperatorsKeeper),
operators.NewAppModule(appCodec, app.OperatorsKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
pools.NewAppModule(appCodec, app.PoolsKeeper),
restaking.NewAppModule(appCodec, app.RestakingKeeper),
assets.NewAppModule(appCodec, app.AssetsKeeper),
Expand Down Expand Up @@ -210,6 +210,7 @@ func simulationModules(

// MilkyWay modules
services.NewAppModule(appCodec, app.ServicesKeeper, app.AccountKeeper, app.BankKeeper),
operators.NewAppModule(appCodec, app.OperatorsKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
}
}

Expand Down
19 changes: 18 additions & 1 deletion testutils/simtesting/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package simtesting

import (
"math/rand"
"time"

sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/baseapp"
Expand All @@ -18,7 +19,11 @@ import (

// SendMsg sends a transaction with the specified message.
func SendMsg(
r *rand.Rand, moduleName string, app *baseapp.BaseApp, ak authkeeper.AccountKeeper, bk bankkeeper.Keeper,
r *rand.Rand,
moduleName string,
app *baseapp.BaseApp,
ak authkeeper.AccountKeeper,
bk bankkeeper.Keeper,
msg sdk.Msg, ctx sdk.Context,
simAccount simtypes.Account,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
Expand Down Expand Up @@ -63,6 +68,18 @@ func GetSimAccount(address sdk.Address, accs []simtypes.Account) (simtypes.Accou
return simtypes.Account{}, false
}

// --------------------------------------------------------------------------------------------------------------------

// RandomFutureTime returns a random future time
func RandomFutureTime(r *rand.Rand, currentTime time.Time) time.Time {
return currentTime.Add(time.Duration(r.Int63n(1e9)))
}

// RandomDuration returns a random duration between the min and max
func RandomDuration(r *rand.Rand, min time.Duration, max time.Duration) time.Duration {
return time.Duration(r.Int63n(int64(max-min))) + min
}

// RandomCoin returns a random coin having the specified denomination and the max given amount
func RandomCoin(r *rand.Rand, denom string, maxAmount int) sdk.Coin {
return sdk.NewCoin(
Expand Down
10 changes: 10 additions & 0 deletions utils/slices.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import (
"slices"
)

// Find returns the first element in the slice that satisfies the given predicate, or false if none is found.
func Find[T any](slice []T, searchFunction func(T) bool) (*T, bool) {
for i, v := range slice {
if searchFunction(v) {
return &slice[i], true
}
}
return nil, false
}

// FindDuplicate returns the first duplicate element in the slice.
// If no duplicates are found, it returns nil instead.
func FindDuplicate[T comparable](slice []T) *T {
Expand Down
2 changes: 1 addition & 1 deletion x/operators/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func GetCmdSetOperatorParams() *cobra.Command {
creator := clientCtx.FromAddress.String()

// Create and validate the message
msg := types.NewMsgSetOperatorParams(creator, id, types.NewOperatorParams(commissionRete))
msg := types.NewMsgSetOperatorParams(id, types.NewOperatorParams(commissionRete), creator)
if err = msg.ValidateBasic(); err != nil {
return fmt.Errorf("message validation failed: %w", err)
}
Expand Down
5 changes: 4 additions & 1 deletion x/operators/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ func (k *Keeper) InitGenesis(ctx sdk.Context, state *types.GenesisState) error {

// Store the inactivating operators
for _, entry := range state.UnbondingOperators {
k.setOperatorAsInactivating(ctx, entry.OperatorID, entry.UnbondingCompletionTime)
err = k.setOperatorAsInactivating(ctx, entry.OperatorID, entry.UnbondingCompletionTime)
if err != nil {
return err
}
}

// Store params
Expand Down
4 changes: 2 additions & 2 deletions x/operators/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Keeper struct {

accountKeeper types.AccountKeeper
poolKeeper types.CommunityPoolKeeper
schema collections.Schema
Schema collections.Schema

nextOperatorID collections.Sequence // Next operator ID
operators collections.Map[uint32, types.Operator] // operator ID -> operator
Expand Down Expand Up @@ -85,7 +85,7 @@ func NewKeeper(
if err != nil {
panic(err)
}
k.schema = schema
k.Schema = schema

return k
}
Expand Down
78 changes: 57 additions & 21 deletions x/operators/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,6 @@ func (suite *KeeperTestSuite) TestMsgServer_DeleteOperator() {
}

func (suite *KeeperTestSuite) TestMsgServer_SetOperatorParams() {
testOperatorId := uint32(2)
operatorAdmin := "cosmos167x6ehhple8gwz5ezy9x0464jltvdpzl6qfdt4"

testCases := []struct {
Expand All @@ -895,37 +894,85 @@ func (suite *KeeperTestSuite) TestMsgServer_SetOperatorParams() {
}{
{
name: "set invalid params fails",
store: func(ctx sdk.Context) {
// Register a test operator
err := suite.k.CreateOperator(ctx, types.NewOperator(
1,
types.OPERATOR_STATUS_ACTIVE,
"MilkyWay Operator",
"https://milkyway.com",
"https://milkyway.com/picture",
operatorAdmin,
))
suite.Require().NoError(err)
},
msg: types.NewMsgSetOperatorParams(
operatorAdmin,
testOperatorId,
1,
types.NewOperatorParams(sdkmath.LegacyNewDec(-1)),
operatorAdmin,
),
shouldErr: true,
},
{
name: "not admin can't set params",
store: func(ctx sdk.Context) {
// Register a test operator
err := suite.k.CreateOperator(ctx, types.NewOperator(
1,
types.OPERATOR_STATUS_ACTIVE,
"MilkyWay Operator",
"https://milkyway.com",
"https://milkyway.com/picture",
operatorAdmin,
))
suite.Require().NoError(err)
},
msg: types.NewMsgSetOperatorParams(
"cosmos1d03wa9qd8flfjtvldndw5csv94tvg5hzfcmcgn",
testOperatorId,
1,
types.NewOperatorParams(sdkmath.LegacyMustNewDecFromStr("0.2")),
"cosmos1d03wa9qd8flfjtvldndw5csv94tvg5hzfcmcgn",
),
shouldErr: true,
},
{
name: "set params for not existing operator fails",
store: func(ctx sdk.Context) {
// Register a test operator
err := suite.k.CreateOperator(ctx, types.NewOperator(
1,
types.OPERATOR_STATUS_ACTIVE,
"MilkyWay Operator",
"https://milkyway.com",
"https://milkyway.com/picture",
operatorAdmin,
))
suite.Require().NoError(err)
},
msg: types.NewMsgSetOperatorParams(
operatorAdmin,
3,
types.NewOperatorParams(sdkmath.LegacyMustNewDecFromStr("0.2")),
operatorAdmin,
),
shouldErr: true,
},
{
name: "set params works properly",
store: func(ctx sdk.Context) {
// Register a test operator
err := suite.k.CreateOperator(ctx, types.NewOperator(
1,
types.OPERATOR_STATUS_ACTIVE,
"MilkyWay Operator",
"https://milkyway.com",
"https://milkyway.com/picture",
operatorAdmin,
))
suite.Require().NoError(err)
},
msg: types.NewMsgSetOperatorParams(
operatorAdmin,
testOperatorId,
1,
types.NewOperatorParams(sdkmath.LegacyMustNewDecFromStr("0.2")),
operatorAdmin,
),
expEvents: []sdk.Event{
sdk.NewEvent(
Expand All @@ -934,7 +981,7 @@ func (suite *KeeperTestSuite) TestMsgServer_SetOperatorParams() {
},
shouldErr: false,
check: func(ctx sdk.Context) {
params, err := suite.k.GetOperatorParams(ctx, testOperatorId)
params, err := suite.k.GetOperatorParams(ctx, 1)
suite.Require().Nil(err)
suite.Require().Equal(types.NewOperatorParams(
sdkmath.LegacyMustNewDecFromStr("0.2"),
Expand All @@ -947,23 +994,12 @@ func (suite *KeeperTestSuite) TestMsgServer_SetOperatorParams() {
tc := tc
suite.Run(tc.name, func() {
suite.SetupTest()
ctx := suite.ctx

ctx, _ := suite.ctx.CacheContext()
if tc.store != nil {
tc.store(ctx)
}

// Register a test operator
err := suite.k.CreateOperator(ctx, types.NewOperator(
testOperatorId,
types.OPERATOR_STATUS_ACTIVE,
"MilkyWay Operator",
"https://milkyway.com",
"https://milkyway.com/picture",
operatorAdmin,
))
suite.Require().NoError(err)

msgServer := keeper.NewMsgServer(suite.k)
res, err := msgServer.SetOperatorParams(ctx, tc.msg)
if tc.shouldErr {
Expand Down
58 changes: 53 additions & 5 deletions x/operators/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
"fmt"

"cosmossdk.io/core/appmodule"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"

Expand All @@ -19,6 +23,7 @@ import (

"github.com/milkyway-labs/milkyway/x/operators/client/cli"
"github.com/milkyway-labs/milkyway/x/operators/keeper"
"github.com/milkyway-labs/milkyway/x/operators/simulation"
"github.com/milkyway-labs/milkyway/x/operators/types"
)

Expand All @@ -27,8 +32,9 @@ const (
)

var (
_ appmodule.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ appmodule.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ module.AppModuleSimulation = AppModule{}
)

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -93,14 +99,27 @@ func (a AppModuleBasic) GetTxCmd() *cobra.Command {
type AppModule struct {
AppModuleBasic

// To ensure setting hooks properly, keeper must be a reference
keeper *keeper.Keeper
}

func NewAppModule(cdc codec.Codec, keeper *keeper.Keeper) AppModule {
// To ensure setting hooks properly, keeper must be a reference
accountKeeper authkeeper.AccountKeeper
bankKeeper bankkeeper.Keeper
stakingKeeper *stakingkeeper.Keeper
}

func NewAppModule(
cdc codec.Codec,
keeper *keeper.Keeper,
accountKeeper authkeeper.AccountKeeper,
bankKeeper bankkeeper.Keeper,
stakingKeeper *stakingkeeper.Keeper,
) AppModule {
return AppModule{
AppModuleBasic: NewAppModuleBasic(cdc),
keeper: keeper,
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
stakingKeeper: stakingKeeper,
}
}

Expand Down Expand Up @@ -150,3 +169,32 @@ func (am AppModule) BeginBlock(ctx context.Context) error {
func (am AppModule) IsOnePerModuleType() {}

func (am AppModule) IsAppModule() {}

// ----------------------------------------------------------------------------
// AppModuleSimulation
// ----------------------------------------------------------------------------

hallazzang marked this conversation as resolved.
Show resolved Hide resolved
// GenerateGenesisState creates a randomized GenState of the services module.
func (AppModule) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}

// ProposalMsgs returns msgs used for governance proposals for simulations.
func (am AppModule) ProposalMsgs(simState module.SimulationState) []simtypes.WeightedProposalMsg {
return simulation.ProposalMsgs(am.stakingKeeper)
}

// RegisterStoreDecoder registers a decoder for services module's types.
func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {
sdr[types.StoreKey] = simulation.NewDecodeStore(am.keeper)
}

// WeightedOperations returns the all the services module operations with their respective weights.
func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation {
return simulation.WeightedOperations(
simState.AppParams,
am.accountKeeper,
am.bankKeeper,
am.keeper,
)
}
37 changes: 37 additions & 0 deletions x/operators/simulation/decoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package simulation

import (
"bytes"
"fmt"

"github.com/cosmos/cosmos-sdk/types/kv"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"

"github.com/milkyway-labs/milkyway/x/operators/keeper"
"github.com/milkyway-labs/milkyway/x/operators/types"
)

// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's
// Value to the corresponding operators type.
func NewDecodeStore(keeper *keeper.Keeper) func(kvA kv.Pair, kvB kv.Pair) string {
collectionsDecoder := simtypes.NewStoreDecoderFuncFromCollectionsSchema(keeper.Schema)

return func(kvA, kvB kv.Pair) string {
switch {
case bytes.Equal(kvA.Key[:1], types.ParamsKey),
bytes.Equal(kvA.Key[:1], types.NextOperatorIDKey),
bytes.Equal(kvA.Key[:1], types.OperatorPrefix),
bytes.Equal(kvA.Key[:1], types.OperatorAddressSetPrefix),
bytes.Equal(kvA.Key[:1], types.OperatorParamsMapPrefix):
return collectionsDecoder(kvA, kvB)

case bytes.Equal(kvA.Key[:1], types.InactivatingOperatorQueuePrefix):
valueA := types.GetOperatorIDFromBytes(kvA.Value)
valueB := types.GetOperatorIDFromBytes(kvB.Value)
return fmt.Sprintf("operatorIDA: %d\noperatorIDB: %d", valueA, valueB)

default:
panic(fmt.Sprintf("invalid operators key prefix %X", kvA.Key[:1]))
}
}
}
Loading
Loading