Skip to content

Commit

Permalink
feat(tests): add operators simulation tests (#190)
Browse files Browse the repository at this point in the history
## Description

This PR adds the simulation tests for the `x/operators` module.  

Closes: MILK-37

<!-- Add a description of the changes that this PR introduces and the
files that
are the most critical to review. -->

---

### Author Checklist

*All items are required. Please add a note to the item if the item is
not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type
prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json)
in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR
Targeting](https://github.com/milkyway-labs/milkyway/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building
modules](https://docs.cosmos.network/v0.44/building-modules/intro.html)
- [ ] included the necessary unit and integration
[tests](https://github.com/milkyway-labs/milkyway/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go
code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable
and please add
your handle next to the items reviewed if you only reviewed selected
items.*

I have...

- [ ] confirmed the correct [type
prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json)
in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
  • Loading branch information
RiccardoM authored Nov 22, 2024
1 parent 56da117 commit 8bacbca
Show file tree
Hide file tree
Showing 16 changed files with 802 additions and 38 deletions.
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
// ----------------------------------------------------------------------------

// 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

0 comments on commit 8bacbca

Please sign in to comment.