Pre-requisite Readings
Synopsis
This document details how to define each module simulation functions to be integrated with the applicationSimulationManager
.
Simulation package
Every module that implements the Cosmos SDK simulator needs to have ax/<module>/simulation
package which contains the primary functions required by the fuzz tests: store
decoders, randomized genesis state and parameters, weighted operations and proposal
contents.
Store decoders
Registering the store decoders is required for theAppImportExport
. This allows
for the key-value pairs from the stores to be decoded (i.e unmarshalled)
to their corresponding types. In particular, it matches the key to a concrete type
and then unmarshals the value from the KVPair
to the type provided.
You can use the example here from the distribution module to implement your store decoders.
Randomized genesis
The simulator tests different scenarios and values for genesis parameters in order to fully test the edge cases of specific modules. Thesimulator
package from each module must expose a RandomizedGenState
function to generate the initial random GenesisState
from a given seed.
Once the module genesis parameter are generated randomly (or with the key and
values defined in a params
file), they are marshaled to JSON format and added
to the app genesis JSON to use it on the simulations.
You can check an example on how to create the randomized genesis here.
Randomized parameter changes
The simulator is able to test parameter changes at random. The simulator package from each module must contain aRandomizedParams
func that will simulate parameter changes of the module throughout the simulations lifespan.
You can see how an example of what is needed to fully test parameter changes here
Random weighted operations
Operations are one of the crucial parts of the Cosmos SDK simulation. They are the transactions (Msg
) that are simulated with random field values. The sender of the operation
is also assigned randomly.
Operations on the simulation are simulated using the full transaction cycle of a
ABCI
application that exposes the BaseApp
.
Shown below is how weights are set:
Copy
Ask AI
package simulation
import (
"bytes"
"fmt"
"math/rand"
"cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
/ Simulation operation weights constants
const (
DefaultWeightMsgCreateValidator int = 100
DefaultWeightMsgEditValidator int = 5
DefaultWeightMsgDelegate int = 100
DefaultWeightMsgUndelegate int = 100
DefaultWeightMsgBeginRedelegate int = 100
DefaultWeightMsgCancelUnbondingDelegation int = 100
OpWeightMsgCreateValidator = "op_weight_msg_create_validator"
OpWeightMsgEditValidator = "op_weight_msg_edit_validator"
OpWeightMsgDelegate = "op_weight_msg_delegate"
OpWeightMsgUndelegate = "op_weight_msg_undelegate"
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
OpWeightMsgCancelUnbondingDelegation = "op_weight_msg_cancel_unbonding_delegation"
)
/ WeightedOperations returns all the operations from the module with their respective weights
func WeightedOperations(
appParams simtypes.AppParams,
cdc codec.JSONCodec,
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simulation.WeightedOperations {
var (
weightMsgCreateValidator int
weightMsgEditValidator int
weightMsgDelegate int
weightMsgUndelegate int
weightMsgBeginRedelegate int
weightMsgCancelUnbondingDelegation int
)
appParams.GetOrGenerate(OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil, func(_ *rand.Rand) {
weightMsgCreateValidator = DefaultWeightMsgCreateValidator
})
appParams.GetOrGenerate(OpWeightMsgEditValidator, &weightMsgEditValidator, nil, func(_ *rand.Rand) {
weightMsgEditValidator = DefaultWeightMsgEditValidator
})
appParams.GetOrGenerate(OpWeightMsgDelegate, &weightMsgDelegate, nil, func(_ *rand.Rand) {
weightMsgDelegate = DefaultWeightMsgDelegate
})
appParams.GetOrGenerate(OpWeightMsgUndelegate, &weightMsgUndelegate, nil, func(_ *rand.Rand) {
weightMsgUndelegate = DefaultWeightMsgUndelegate
})
appParams.GetOrGenerate(OpWeightMsgBeginRedelegate, &weightMsgBeginRedelegate, nil, func(_ *rand.Rand) {
weightMsgBeginRedelegate = DefaultWeightMsgBeginRedelegate
})
appParams.GetOrGenerate(OpWeightMsgCancelUnbondingDelegation, &weightMsgCancelUnbondingDelegation, nil, func(_ *rand.Rand) {
weightMsgCancelUnbondingDelegation = DefaultWeightMsgCancelUnbondingDelegation
})
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgCreateValidator,
SimulateMsgCreateValidator(txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgEditValidator,
SimulateMsgEditValidator(txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgDelegate,
SimulateMsgDelegate(txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgUndelegate,
SimulateMsgUndelegate(txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgBeginRedelegate,
SimulateMsgBeginRedelegate(txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightMsgCancelUnbondingDelegation,
SimulateMsgCancelUnbondingDelegate(txGen, ak, bk, k),
),
}
}
/ SimulateMsgCreateValidator generates a MsgCreateValidator with random values
func SimulateMsgCreateValidator(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgCreateValidator{
})
simAccount, _ := simtypes.RandomAcc(r, accs)
address := sdk.ValAddress(simAccount.Address)
/ ensure the validator doesn't exist already
_, err := k.GetValidator(ctx, address)
if err == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator already exists"), nil, nil
}
denom, err := k.BondDenom(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
}
balance := bk.GetBalance(ctx, simAccount.Address, denom).Amount
if !balance.IsPositive() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil
}
amount, err := simtypes.RandPositiveInt(r, balance)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err
}
selfDelegation := sdk.NewCoin(denom, amount)
account := ak.GetAccount(ctx, simAccount.Address)
spendable := bk.SpendableCoins(ctx, account.GetAddress())
var fees sdk.Coins
coins, hasNeg := spendable.SafeSub(selfDelegation)
if !hasNeg {
fees, err = simtypes.RandomFees(r, ctx, coins)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err
}
}
description := types.NewDescription(
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
)
maxCommission := math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2)
commission := types.NewCommissionRates(
simtypes.RandomDecAmount(r, maxCommission),
maxCommission,
simtypes.RandomDecAmount(r, maxCommission),
)
msg, err := types.NewMsgCreateValidator(address.String(), simAccount.ConsKey.PubKey(), selfDelegation, description, commission, math.OneInt())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to create CreateValidator message"), nil, err
}
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
ModuleName: types.ModuleName,
}
return simulation.GenAndDeliverTx(txCtx, fees)
}
}
/ SimulateMsgEditValidator generates a MsgEditValidator with random values
func SimulateMsgEditValidator(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgEditValidator{
})
vals, err := k.GetAllValidators(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
}
if len(vals) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
}
val, ok := testutil.RandSliceElem(r, vals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil
}
address := val.GetOperator()
newCommissionRate := simtypes.RandomDecAmount(r, val.Commission.MaxRate)
if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.BlockHeader().Time); err != nil {
/ skip as the commission is invalid
return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid commission rate"), nil, nil
}
bz, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
}
simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(bz))
if !found {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator())
}
account := ak.GetAccount(ctx, simAccount.Address)
spendable := bk.SpendableCoins(ctx, account.GetAddress())
description := types.NewDescription(
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
simtypes.RandStringOfLength(r, 10),
)
msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil)
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
Bankkeeper: bk,
ModuleName: types.ModuleName,
CoinsSpentInMsg: spendable,
}
return simulation.GenAndDeliverTxWithRandFees(txCtx)
}
}
/ SimulateMsgDelegate generates a MsgDelegate with random values
func SimulateMsgDelegate(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgDelegate{
})
denom, err := k.BondDenom(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
}
vals, err := k.GetAllValidators(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
}
if len(vals) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
}
simAccount, _ := simtypes.RandomAcc(r, accs)
val, ok := testutil.RandSliceElem(r, vals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil
}
if val.InvalidExRate() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator's invalid echange rate"), nil, nil
}
amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount
if !amount.IsPositive() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil
}
amount, err = simtypes.RandPositiveInt(r, amount)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err
}
bondAmt := sdk.NewCoin(denom, amount)
account := ak.GetAccount(ctx, simAccount.Address)
spendable := bk.SpendableCoins(ctx, account.GetAddress())
var fees sdk.Coins
coins, hasNeg := spendable.SafeSub(bondAmt)
if !hasNeg {
fees, err = simtypes.RandomFees(r, ctx, coins)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err
}
}
msg := types.NewMsgDelegate(simAccount.Address.String(), val.GetOperator(), bondAmt)
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
ModuleName: types.ModuleName,
}
return simulation.GenAndDeliverTx(txCtx, fees)
}
}
/ SimulateMsgUndelegate generates a MsgUndelegate with random values
func SimulateMsgUndelegate(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgUndelegate{
})
vals, err := k.GetAllValidators(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
}
if len(vals) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
}
val, ok := testutil.RandSliceElem(r, vals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil
}
valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
}
delegations, err := k.GetValidatorDelegations(ctx, valAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil
}
if delegations == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil
}
/ get random delegator from validator
delegation := delegations[r.Intn(len(delegations))]
delAddr := delegation.GetDelegatorAddr()
delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err
}
hasMaxUD, err := k.HasMaxUnbondingDelegationEntries(ctx, delAddrBz, valAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max unbonding delegation entries"), nil, err
}
if hasMaxUD {
return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have a max unbonding delegation entries"), nil, nil
}
totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil
}
unbondAmt, err := simtypes.RandPositiveInt(r, totalBond)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid unbond amount"), nil, err
}
if unbondAmt.IsZero() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unbond amount is zero"), nil, nil
}
bondDenom, err := k.BondDenom(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
}
msg := types.NewMsgUndelegate(
delAddr, val.GetOperator(), sdk.NewCoin(bondDenom, unbondAmt),
)
/ need to retrieve the simulation account associated with delegation to retrieve PrivKey
var simAccount simtypes.Account
for _, simAcc := range accs {
if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) {
simAccount = simAcc
break
}
}
/ if simaccount.PrivKey == nil, delegation address does not exist in accs. However, since smart contracts and module accounts can stake, we can ignore the error
if simAccount.PrivKey == nil {
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "account private key is nil"), nil, nil
}
account := ak.GetAccount(ctx, delAddrBz)
spendable := bk.SpendableCoins(ctx, account.GetAddress())
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
Bankkeeper: bk,
ModuleName: types.ModuleName,
CoinsSpentInMsg: spendable,
}
return simulation.GenAndDeliverTxWithRandFees(txCtx)
}
}
/ SimulateMsgCancelUnbondingDelegate generates a MsgCancelUnbondingDelegate with random values
func SimulateMsgCancelUnbondingDelegate(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{
})
vals, err := k.GetAllValidators(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
}
if len(vals) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
}
simAccount, _ := simtypes.RandomAcc(r, accs)
val, ok := testutil.RandSliceElem(r, vals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil
}
if val.IsJailed() || val.InvalidExRate() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is jailed"), nil, nil
}
valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
}
unbondingDelegation, err := k.GetUnbondingDelegation(ctx, simAccount.Address, valAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "account does have any unbonding delegation"), nil, nil
}
/ This is a temporary fix to make staking simulation pass. We should fetch
/ the first unbondingDelegationEntry that matches the creationHeight, because
/ currently the staking msgServer chooses the first unbondingDelegationEntry
/ with the matching creationHeight.
/
/ ref: https://github.com/cosmos/cosmos-sdk/issues/12932
creationHeight := unbondingDelegation.Entries[r.Intn(len(unbondingDelegation.Entries))].CreationHeight
var unbondingDelegationEntry types.UnbondingDelegationEntry
for _, entry := range unbondingDelegation.Entries {
if entry.CreationHeight == creationHeight {
unbondingDelegationEntry = entry
break
}
}
if unbondingDelegationEntry.CompletionTime.Before(ctx.BlockTime()) {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unbonding delegation is already processed"), nil, nil
}
if !unbondingDelegationEntry.Balance.IsPositive() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "delegator receiving balance is negative"), nil, nil
}
cancelBondAmt := simtypes.RandomAmount(r, unbondingDelegationEntry.Balance)
if cancelBondAmt.IsZero() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "cancelBondAmt amount is zero"), nil, nil
}
bondDenom, err := k.BondDenom(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
}
msg := types.NewMsgCancelUnbondingDelegation(
simAccount.Address.String(), val.GetOperator(), unbondingDelegationEntry.CreationHeight, sdk.NewCoin(bondDenom, cancelBondAmt),
)
spendable := bk.SpendableCoins(ctx, simAccount.Address)
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
Bankkeeper: bk,
ModuleName: types.ModuleName,
CoinsSpentInMsg: spendable,
}
return simulation.GenAndDeliverTxWithRandFees(txCtx)
}
}
/ SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
func SimulateMsgBeginRedelegate(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
)
simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
msgType := sdk.MsgTypeURL(&types.MsgBeginRedelegate{
})
allVals, err := k.GetAllValidators(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err
}
if len(allVals) == 0 {
return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil
}
srcVal, ok := testutil.RandSliceElem(r, allVals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil
}
srcAddr, err := k.ValidatorAddressCodec().StringToBytes(srcVal.GetOperator())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
}
delegations, err := k.GetValidatorDelegations(ctx, srcAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil
}
if delegations == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil
}
/ get random delegator from src validator
delegation := delegations[r.Intn(len(delegations))]
delAddr := delegation.GetDelegatorAddr()
delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err
}
hasRecRedel, err := k.HasReceivingRedelegation(ctx, delAddrBz, srcAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting receiving redelegation"), nil, err
}
if hasRecRedel {
return simtypes.NoOpMsg(types.ModuleName, msgType, "receveing redelegation is not allowed"), nil, nil / skip
}
/ get random destination validator
destVal, ok := testutil.RandSliceElem(r, allVals)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil
}
destAddr, err := k.ValidatorAddressCodec().StringToBytes(destVal.GetOperator())
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err
}
hasMaxRedel, err := k.HasMaxRedelegationEntries(ctx, delAddrBz, srcAddr, destAddr)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max redelegation entries"), nil, err
}
if bytes.Equal(srcAddr, destAddr) || destVal.InvalidExRate() || hasMaxRedel {
return simtypes.NoOpMsg(types.ModuleName, msgType, "checks failed"), nil, nil
}
totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt()
if !totalBond.IsPositive() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil
}
redAmt, err := simtypes.RandPositiveInt(r, totalBond)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err
}
if redAmt.IsZero() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "amount is zero"), nil, nil
}
/ check if the shares truncate to zero
shares, err := srcVal.SharesFromTokens(redAmt)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid shares"), nil, err
}
if srcVal.TokensFromShares(shares).TruncateInt().IsZero() {
return simtypes.NoOpMsg(types.ModuleName, msgType, "shares truncate to zero"), nil, nil / skip
}
/ need to retrieve the simulation account associated with delegation to retrieve PrivKey
var simAccount simtypes.Account
for _, simAcc := range accs {
if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) {
simAccount = simAcc
break
}
}
/ if simaccount.PrivKey == nil, delegation address does not exist in accs. However, since smart contracts and module accounts can stake, we can ignore the error
if simAccount.PrivKey == nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "account private key is nil"), nil, nil
}
account := ak.GetAccount(ctx, delAddrBz)
spendable := bk.SpendableCoins(ctx, account.GetAddress())
bondDenom, err := k.BondDenom(ctx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err
}
msg := types.NewMsgBeginRedelegate(
delAddr, srcVal.GetOperator(), destVal.GetOperator(),
sdk.NewCoin(bondDenom, redAmt),
)
txCtx := simulation.OperationInput{
R: r,
App: app,
TxGen: txGen,
Cdc: nil,
Msg: msg,
Context: ctx,
SimAccount: simAccount,
AccountKeeper: ak,
Bankkeeper: bk,
ModuleName: types.ModuleName,
CoinsSpentInMsg: spendable,
}
return simulation.GenAndDeliverTxWithRandFees(txCtx)
}
}
*rand.Rand
to define a random weight for the operation, or you can inject your own predefined weights.
Here is how one can override the above package simappparams
.
Copy
Ask AI
#!/usr/bin/make -f
PACKAGES_NOSIMULATION=$(shell go list ./... | grep -v '/simulation')
PACKAGES_SIMTEST=$(shell go list ./... | grep '/simulation')
export VERSION := $(shell echo $(shell git describe --tags --always --match "v*") | sed 's/^v/')
export CMTVERSION := $(shell go list -m github.com/cometbft/cometbft | sed 's:.* ::')
export COMMIT := $(shell git log -1 --format='%H')
LEDGER_ENABLED ?= true
BINDIR ?= $(GOPATH)/bin
BUILDDIR ?= $(CURDIR)/build
SIMAPP = ./simapp
MOCKS_DIR = $(CURDIR)/tests/mocks
HTTPS_GIT := https://github.com/cosmos/cosmos-sdk.git
DOCKER := $(shell which docker)
PROJECT_NAME = $(shell git remote get-url origin | xargs basename -s .git)
# process build tags
build_tags = netgo
ifeq ($(LEDGER_ENABLED),true)
ifeq ($(OS),Windows_NT)
GCCEXE = $(shell where gcc.exe 2> NUL)
ifeq ($(GCCEXE),)
$(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
else
UNAME_S = $(shell uname -s)
ifeq ($(UNAME_S),OpenBSD)
$(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988))
else
GCC = $(shell command -v gcc 2> /dev/null)
ifeq ($(GCC),)
$(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false)
else
build_tags += ledger
endif
endif
endif
endif
ifeq (secp,$(findstring secp,$(COSMOS_BUILD_OPTIONS)))
build_tags += libsecp256k1_sdk
endif
ifeq (legacy,$(findstring legacy,$(COSMOS_BUILD_OPTIONS)))
build_tags += app_v1
endif
whitespace :=
whitespace += $(whitespace)
comma := ,
build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags))
# process linker flags
ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sim \
-X github.com/cosmos/cosmos-sdk/version.AppName=simd \
-X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \
-X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \
-X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" \
-X github.com/cometbft/cometbft/version.TMCoreSemVer=$(CMTVERSION)
# DB backend selection
ifeq (cleveldb,$(findstring cleveldb,$(COSMOS_BUILD_OPTIONS)))
build_tags += gcc
endif
ifeq (badgerdb,$(findstring badgerdb,$(COSMOS_BUILD_OPTIONS)))
build_tags += badgerdb
endif
# handle rocksdb
ifeq (rocksdb,$(findstring rocksdb,$(COSMOS_BUILD_OPTIONS)))
CGO_ENABLED=1
build_tags += rocksdb
endif
# handle boltdb
ifeq (boltdb,$(findstring boltdb,$(COSMOS_BUILD_OPTIONS)))
build_tags += boltdb
endif
ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS)))
ldflags += -w -s
endif
ldflags += $(LDFLAGS)
ldflags := $(strip $(ldflags))
build_tags += $(BUILD_TAGS)
build_tags := $(strip $(build_tags))
BUILD_FLAGS := -tags "$(build_tags)" -ldflags '$(ldflags)'
# check for nostrip option
ifeq (,$(findstring nostrip,$(COSMOS_BUILD_OPTIONS)))
BUILD_FLAGS += -trimpath
endif
# Check for debug option
ifeq (debug,$(findstring debug,$(COSMOS_BUILD_OPTIONS)))
BUILD_FLAGS += -gcflags "all=-N -l"
endif
all: tools build lint test vulncheck
# The below include contains the tools and runsim targets.
include contrib/devtools/Makefile
###############################################################################
### Build ###
###############################################################################
BUILD_TARGETS := build install
build: BUILD_ARGS=-o $(BUILDDIR)/
build-linux-amd64:
GOOS=linux GOARCH=amd64 LEDGER_ENABLED=false $(MAKE)
build
build-linux-arm64:
GOOS=linux GOARCH=arm64 LEDGER_ENABLED=false $(MAKE)
build
$(BUILD_TARGETS): go.sum $(BUILDDIR)/
cd ${
CURRENT_DIR
}/simapp && go $@ -mod=readonly $(BUILD_FLAGS) $(BUILD_ARGS) ./...
$(BUILDDIR)/:
mkdir -p $(BUILDDIR)/
cosmovisor:
$(MAKE) -C tools/cosmovisor cosmovisor
confix:
$(MAKE) -C tools/confix confix
hubl:
$(MAKE) -C tools/hubl hubl
.PHONY: build build-linux-amd64 build-linux-arm64 cosmovisor confix
mocks: $(MOCKS_DIR)
@go install github.com/golang/mock/mockgen@v1.6.0
sh ./scripts/mockgen.sh
.PHONY: mocks
vulncheck: $(BUILDDIR)/
GOBIN=$(BUILDDIR)
go install golang.org/x/vuln/cmd/govulncheck@latest
$(BUILDDIR)/govulncheck ./...
$(MOCKS_DIR):
mkdir -p $(MOCKS_DIR)
distclean: clean tools-clean
clean:
rm -rf \
$(BUILDDIR)/ \
artifacts/ \
tmp-swagger-gen/ \
.testnets
.PHONY: distclean clean
###############################################################################
### Tools & Dependencies ###
###############################################################################
go.sum: go.mod
echo "Ensure dependencies have not been modified ..." >&2
go mod verify
go mod tidy
###############################################################################
### Documentation ###
###############################################################################
godocs:
@echo "--> Wait a few seconds and visit http:/localhost:6060/pkg/github.com/cosmos/cosmos-sdk/types"
go install golang.org/x/tools/cmd/godoc@latest
godoc -http=:6060
build-docs:
@cd docs && DOCS_DOMAIN=docs.cosmos.network sh ./build-all.sh
.PHONY: build-docs
###############################################################################
### Tests & Simulation ###
###############################################################################
# make init-simapp initializes a single local node network
# it is useful for testing and development
# Usage: make install && make init-simapp && simd start
# Warning: make init-simapp will remove all data in simapp home directory
init-simapp:
./scripts/init-simapp.sh
test: test-unit
test-e2e:
$(MAKE) -C tests test-e2e
test-e2e-cov:
$(MAKE) -C tests test-e2e-cov
test-integration:
$(MAKE) -C tests test-integration
test-integration-cov:
$(MAKE) -C tests test-integration-cov
test-all: test-unit test-e2e test-integration test-ledger-mock test-race
TEST_PACKAGES=./...
TEST_TARGETS := test-unit test-unit-amino test-unit-proto test-ledger-mock test-race test-ledger test-race
# Test runs-specific rules. To add a new test target, just add
# a new rule, customise ARGS or TEST_PACKAGES ad libitum, and
# append the new rule to the TEST_TARGETS list.
test-unit: test_tags += cgo ledger test_ledger_mock norace
test-unit-amino: test_tags += ledger test_ledger_mock test_amino norace
test-ledger: test_tags += cgo ledger norace
test-ledger-mock: test_tags += ledger test_ledger_mock norace
test-race: test_tags += cgo ledger test_ledger_mock
test-race: ARGS=-race
test-race: TEST_PACKAGES=$(PACKAGES_NOSIMULATION)
$(TEST_TARGETS): run-tests
# check-* compiles and collects tests without running them
# note: go test -c doesn't support multiple packages yet (https://github.com/golang/go/issues/15513)
CHECK_TEST_TARGETS := check-test-unit check-test-unit-amino
check-test-unit: test_tags += cgo ledger test_ledger_mock norace
check-test-unit-amino: test_tags += ledger test_ledger_mock test_amino norace
$(CHECK_TEST_TARGETS): EXTRA_ARGS=-run=none
$(CHECK_TEST_TARGETS): run-tests
ARGS += -tags "$(test_tags)"
SUB_MODULES = $(shell find . -type f -name 'go.mod' -print0 | xargs -0 -n1 dirname | sort)
CURRENT_DIR = $(shell pwd)
run-tests:
ifneq (,$(shell which tparse 2>/dev/null))
@echo "Starting unit tests"; \
finalec=0; \
for module in $(SUB_MODULES); do \
cd ${
CURRENT_DIR
}/$module; \
echo "Running unit tests for $(grep '^module' go.mod)"; \
go test -mod=readonly -json $(ARGS) $(TEST_PACKAGES) ./... | tparse; \
ec=$?; \
if [ "$ec" -ne '0' ]; then finalec=$ec; fi; \
done; \
exit $finalec
else
@echo "Starting unit tests"; \
finalec=0; \
for module in $(SUB_MODULES); do \
cd ${
CURRENT_DIR
}/$module; \
echo "Running unit tests for $(grep '^module' go.mod)"; \
go test -mod=readonly $(ARGS) $(TEST_PACKAGES) ./... ; \
ec=$?; \
if [ "$ec" -ne '0' ]; then finalec=$ec; fi; \
done; \
exit $finalec
endif
.PHONY: run-tests test test-all $(TEST_TARGETS)
test-sim-nondeterminism:
@echo "Running non-determinism test..."
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -run TestAppStateDeterminism -Enabled=true \
-NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
# example:
# export COSMOS_SDK_ABCI_V1=<path-to-plugin-binary>
# make test-sim-nondeterminism-streaming
#
# Using the built-in examples:
# export COSMOS_SDK_ABCI_V1=<path-to-sdk>/store/streaming/abci/examples/file/file
# make test-sim-nondeterminism-streaming
test-sim-nondeterminism-streaming:
@echo "Running non-determinism-streaming test..."
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -run TestAppStateDeterminism -Enabled=true \
-NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h -EnableStreaming=true
test-sim-custom-genesis-fast:
@echo "Running custom genesis simulation..."
@echo "By default, ${
HOME
}/.simapp/config/genesis.json will be used."
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -run TestFullAppSimulation -Genesis=${
HOME
}/.simapp/config/genesis.json \
-Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -SigverifyTx=false -v -timeout 24h
test-sim-import-export: runsim
@echo "Running application import/export simulation. This may take several minutes..."
@cd ${
CURRENT_DIR
}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 50 5 TestAppImportExport
test-sim-after-import: runsim
@echo "Running application simulation-after-import. This may take several minutes..."
@cd ${
CURRENT_DIR
}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 50 5 TestAppSimulationAfterImport
test-sim-custom-genesis-multi-seed: runsim
@echo "Running multi-seed custom genesis simulation..."
@echo "By default, ${
HOME
}/.simapp/config/genesis.json will be used."
@cd ${
CURRENT_DIR
}/simapp && $(BINDIR)/runsim -Genesis=${
HOME
}/.simapp/config/genesis.json -SigverifyTx=false -SimAppPkg=. -ExitOnFail 400 5 TestFullAppSimulation
test-sim-multi-seed-long: runsim
@echo "Running long multi-seed application simulation. This may take awhile!"
@cd ${
CURRENT_DIR
}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 500 50 TestFullAppSimulation
test-sim-multi-seed-short: runsim
@echo "Running short multi-seed application simulation. This may take awhile!"
@cd ${
CURRENT_DIR
}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 50 10 TestFullAppSimulation
test-sim-benchmark-invariants:
@echo "Running simulation invariant benchmarks..."
cd ${
CURRENT_DIR
}/simapp && @go test -mod=readonly -benchmem -bench=BenchmarkInvariants -run=^$ \
-Enabled=true -NumBlocks=1000 -BlockSize=200 \
-Period=1 -Commit=true -Seed=57 -v -timeout 24h
.PHONY: \
test-sim-nondeterminism \
test-sim-nondeterminism-streaming \
test-sim-custom-genesis-fast \
test-sim-import-export \
test-sim-after-import \
test-sim-custom-genesis-multi-seed \
test-sim-multi-seed-short \
test-sim-multi-seed-long \
test-sim-benchmark-invariants
SIM_NUM_BLOCKS ?= 500
SIM_BLOCK_SIZE ?= 200
SIM_COMMIT ?= true
test-sim-benchmark:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -run=^$ $(.) -bench ^BenchmarkFullAppSimulation$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
# example:
# export COSMOS_SDK_ABCI_V1=<path-to-plugin-binary>
# make test-sim-benchmark-streaming
#
# Using the built-in examples:
# export COSMOS_SDK_ABCI_V1=<path-to-sdk>/store/streaming/abci/examples/file/file
# make test-sim-benchmark-streaming
test-sim-benchmark-streaming:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -run=^$ $(.) -bench ^BenchmarkFullAppSimulation$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -EnableStreaming=true
test-sim-profile:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -benchmem -run=^$ $(.) -bench ^BenchmarkFullAppSimulation$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
# example:
# export COSMOS_SDK_ABCI_V1=<path-to-plugin-binary>
# make test-sim-profile-streaming
#
# Using the built-in examples:
# export COSMOS_SDK_ABCI_V1=<path-to-sdk>/store/streaming/abci/examples/file/file
# make test-sim-profile-streaming
test-sim-profile-streaming:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${
CURRENT_DIR
}/simapp && go test -mod=readonly -benchmem -run=^$ $(.) -bench ^BenchmarkFullAppSimulation$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out -EnableStreaming=true
.PHONY: test-sim-profile test-sim-benchmark
benchmark:
@go test -mod=readonly -bench=. $(PACKAGES_NOSIMULATION)
.PHONY: benchmark
###############################################################################
### Linting ###
###############################################################################
golangci_version=v1.51.2
lint-install:
@echo "--> Installing golangci-lint $(golangci_version)"
@go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(golangci_version)
lint:
@echo "--> Running linter"
$(MAKE)
lint-install
@./scripts/go-lint-all.bash --timeout=15m
lint-fix:
@echo "--> Running linter"
$(MAKE)
lint-install
@./scripts/go-lint-all.bash --fix
.PHONY: lint lint-fix
###############################################################################
### Protobuf ###
###############################################################################
protoVer=0.14.0
protoImageName=ghcr.io/cosmos/proto-builder:$(protoVer)
protoImage=$(DOCKER)
run --rm -v $(CURDIR):/workspace --workdir /workspace $(protoImageName)
proto-all: proto-format proto-lint proto-gen
proto-gen:
@echo "Generating Protobuf files"
@$(protoImage)
sh ./scripts/protocgen.sh
proto-swagger-gen:
@echo "Generating Protobuf Swagger"
@$(protoImage)
sh ./scripts/protoc-swagger-gen.sh
proto-format:
@$(protoImage)
find ./ -name "*.proto" -exec clang-format -i {
} \;
proto-lint:
@$(protoImage)
buf lint --error-format=json
proto-check-breaking:
@$(protoImage)
buf breaking --against $(HTTPS_GIT)#branch=main
CMT_URL = https://raw.githubusercontent.com/cometbft/cometbft/v0.38.0/proto/tendermint
CMT_CRYPTO_TYPES = proto/tendermint/crypto
CMT_ABCI_TYPES = proto/tendermint/abci
CMT_TYPES = proto/tendermint/types
CMT_VERSION = proto/tendermint/version
CMT_LIBS = proto/tendermint/libs/bits
CMT_P2P = proto/tendermint/p2p
proto-update-deps:
@echo "Updating Protobuf dependencies"
@mkdir -p $(CMT_ABCI_TYPES)
@curl -sSL $(CMT_URL)/abci/types.proto > $(CMT_ABCI_TYPES)/types.proto
@mkdir -p $(CMT_VERSION)
@curl -sSL $(CMT_URL)/version/types.proto > $(CMT_VERSION)/types.proto
@mkdir -p $(CMT_TYPES)
@curl -sSL $(CMT_URL)/types/types.proto > $(CMT_TYPES)/types.proto
@curl -sSL $(CMT_URL)/types/evidence.proto > $(CMT_TYPES)/evidence.proto
@curl -sSL $(CMT_URL)/types/params.proto > $(CMT_TYPES)/params.proto
@curl -sSL $(CMT_URL)/types/validator.proto > $(CMT_TYPES)/validator.proto
@curl -sSL $(CMT_URL)/types/block.proto > $(CMT_TYPES)/block.proto
@mkdir -p $(CMT_CRYPTO_TYPES)
@curl -sSL $(CMT_URL)/crypto/proof.proto > $(CMT_CRYPTO_TYPES)/proof.proto
@curl -sSL $(CMT_URL)/crypto/keys.proto > $(CMT_CRYPTO_TYPES)/keys.proto
@mkdir -p $(CMT_LIBS)
@curl -sSL $(CMT_URL)/libs/bits/types.proto > $(CMT_LIBS)/types.proto
@mkdir -p $(CMT_P2P)
@curl -sSL $(CMT_URL)/p2p/types.proto > $(CMT_P2P)/types.proto
$(DOCKER)
run --rm -v $(CURDIR)/proto:/workspace --workdir /workspace $(protoImageName)
buf mod update
.PHONY: proto-all proto-gen proto-swagger-gen proto-format proto-lint proto-check-breaking proto-update-deps
###############################################################################
### Localnet ###
###############################################################################
localnet-build-env:
$(MAKE) -C contrib/images simd-env
localnet-build-dlv:
$(MAKE) -C contrib/images simd-dlv
localnet-build-nodes:
$(DOCKER)
run --rm -v $(CURDIR)/.testnets:/data cosmossdk/simd \
testnet init-files --v 4 -o /data --starting-ip-address 192.168.10.2 --keyring-backend=test
docker compose up -d
localnet-stop:
docker compose down
# localnet-start will run a 4-node testnet locally. The nodes are
# based off the docker images in: ./contrib/images/simd-env
localnet-start: localnet-stop localnet-build-env localnet-build-nodes
# localnet-debug will run a 4-node testnet locally in debug mode
# you can read more about the debug mode here: ./contrib/images/simd-dlv/README.md
localnet-debug: localnet-stop localnet-build-dlv localnet-build-nodes
.PHONY: localnet-start localnet-stop localnet-debug localnet-build-env localnet-build-dlv localnet-build-nodes
Random proposal contents
Randomized governance proposals are also supported on the Cosmos SDK simulator. Each module must define the governance proposalContent
s that they expose and register
them to be used on the parameters.
Registering simulation functions
Now that all the required functions are defined, we need to integrate them into the module pattern within themodule.go
:
Copy
Ask AI
package distribution
import (
"context"
"encoding/json"
"fmt"
gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
modulev1 "cosmossdk.io/api/cosmos/distribution/module/v1"
"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/store"
"cosmossdk.io/depinject"
sdkclient "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
"github.com/cosmos/cosmos-sdk/x/distribution/exported"
"github.com/cosmos/cosmos-sdk/x/distribution/keeper"
"github.com/cosmos/cosmos-sdk/x/distribution/simulation"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
staking "github.com/cosmos/cosmos-sdk/x/staking/types"
)
/ ConsensusVersion defines the current x/distribution module consensus version.
const ConsensusVersion = 3
var (
_ module.AppModuleBasic = AppModule{
}
_ module.AppModuleSimulation = AppModule{
}
_ module.HasGenesis = AppModule{
}
_ module.HasServices = AppModule{
}
_ module.HasInvariants = AppModule{
}
_ appmodule.AppModule = AppModule{
}
_ appmodule.HasBeginBlocker = AppModule{
}
)
/ AppModuleBasic defines the basic application module used by the distribution module.
type AppModuleBasic struct {
cdc codec.Codec
ac address.Codec
}
/ Name returns the distribution module's name.
func (AppModuleBasic)
Name()
string {
return types.ModuleName
}
/ RegisterLegacyAminoCodec registers the distribution module's types for the given codec.
func (AppModuleBasic)
RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
types.RegisterLegacyAminoCodec(cdc)
}
/ DefaultGenesis returns default genesis state as raw bytes for the distribution
/ module.
func (AppModuleBasic)
DefaultGenesis(cdc codec.JSONCodec)
json.RawMessage {
return cdc.MustMarshalJSON(types.DefaultGenesisState())
}
/ ValidateGenesis performs genesis state validation for the distribution module.
func (AppModuleBasic)
ValidateGenesis(cdc codec.JSONCodec, _ sdkclient.TxEncodingConfig, bz json.RawMessage)
error {
var data types.GenesisState
if err := cdc.UnmarshalJSON(bz, &data); err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
}
return types.ValidateGenesis(&data)
}
/ RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the distribution module.
func (AppModuleBasic)
RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *gwruntime.ServeMux) {
if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil {
panic(err)
}
}
/ GetTxCmd returns the root tx command for the distribution module.
func (ab AppModuleBasic)
GetTxCmd() *cobra.Command {
return cli.NewTxCmd(ab.cdc.InterfaceRegistry().SigningContext().ValidatorAddressCodec(), ab.cdc.InterfaceRegistry().SigningContext().AddressCodec())
}
/ RegisterInterfaces implements InterfaceModule
func (AppModuleBasic)
RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
types.RegisterInterfaces(registry)
}
/ AppModule implements an application module for the distribution module.
type AppModule struct {
AppModuleBasic
keeper keeper.Keeper
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
stakingKeeper types.StakingKeeper
/ legacySubspace is used solely for migration of x/params managed parameters
legacySubspace exported.Subspace
}
/ NewAppModule creates a new AppModule object
func NewAppModule(
cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.AccountKeeper,
bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, ss exported.Subspace,
)
AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{
cdc: cdc, ac: accountKeeper.AddressCodec()
},
keeper: keeper,
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
stakingKeeper: stakingKeeper,
legacySubspace: ss,
}
}
/ IsOnePerModuleType implements the depinject.OnePerModuleType interface.
func (am AppModule)
IsOnePerModuleType() {
}
/ IsAppModule implements the appmodule.AppModule interface.
func (am AppModule)
IsAppModule() {
}
/ RegisterInvariants registers the distribution module invariants.
func (am AppModule)
RegisterInvariants(ir sdk.InvariantRegistry) {
keeper.RegisterInvariants(ir, am.keeper)
}
/ RegisterServices registers module services.
func (am AppModule)
RegisterServices(cfg module.Configurator) {
types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper))
types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQuerier(am.keeper))
m := keeper.NewMigrator(am.keeper, am.legacySubspace)
if err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 1 to 2: %v", types.ModuleName, err))
}
if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil {
panic(fmt.Sprintf("failed to migrate x/%s from version 2 to 3: %v", types.ModuleName, err))
}
}
/ InitGenesis performs genesis initialization for the distribution module. It returns
/ no validator updates.
func (am AppModule)
InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) {
var genesisState types.GenesisState
cdc.MustUnmarshalJSON(data, &genesisState)
am.keeper.InitGenesis(ctx, genesisState)
}
/ ExportGenesis returns the exported genesis state as raw bytes for the distribution
/ module.
func (am AppModule)
ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec)
json.RawMessage {
gs := am.keeper.ExportGenesis(ctx)
return cdc.MustMarshalJSON(gs)
}
/ ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule)
ConsensusVersion()
uint64 {
return ConsensusVersion
}
/ BeginBlock returns the begin blocker for the distribution module.
func (am AppModule)
BeginBlock(ctx context.Context)
error {
c := sdk.UnwrapSDKContext(ctx)
return BeginBlocker(c, am.keeper)
}
/ AppModuleSimulation functions
/ GenerateGenesisState creates a randomized GenState of the distribution module.
func (AppModule)
GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
/ ProposalMsgs returns msgs used for governance proposals for simulations.
func (AppModule)
ProposalMsgs(_ module.SimulationState) []simtypes.WeightedProposalMsg {
return simulation.ProposalMsgs()
}
/ RegisterStoreDecoder registers a decoder for distribution module's types
func (am AppModule)
RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) {
sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc)
}
/ WeightedOperations returns the all the gov module operations with their respective weights.
func (am AppModule)
WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation {
return simulation.WeightedOperations(
simState.AppParams, simState.Cdc, simState.TxConfig,
am.accountKeeper, am.bankKeeper, am.keeper, am.stakingKeeper,
)
}
/
/ App Wiring Setup
/
func init() {
appmodule.Register(&modulev1.Module{
},
appmodule.Provide(ProvideModule),
)
}
type ModuleInputs struct {
depinject.In
Config *modulev1.Module
StoreService store.KVStoreService
Cdc codec.Codec
AccountKeeper types.AccountKeeper
BankKeeper types.BankKeeper
StakingKeeper types.StakingKeeper
/ LegacySubspace is used solely for migration of x/params managed parameters
LegacySubspace exported.Subspace `optional:"true"`
}
type ModuleOutputs struct {
depinject.Out
DistrKeeper keeper.Keeper
Module appmodule.AppModule
Hooks staking.StakingHooksWrapper
}
func ProvideModule(in ModuleInputs)
ModuleOutputs {
feeCollectorName := in.Config.FeeCollectorName
if feeCollectorName == "" {
feeCollectorName = authtypes.FeeCollectorName
}
/ default to governance authority if not provided
authority := authtypes.NewModuleAddress(govtypes.ModuleName)
if in.Config.Authority != "" {
authority = authtypes.NewModuleAddressOrBech32Address(in.Config.Authority)
}
k := keeper.NewKeeper(
in.Cdc,
in.StoreService,
in.AccountKeeper,
in.BankKeeper,
in.StakingKeeper,
feeCollectorName,
authority.String(),
)
m := NewAppModule(in.Cdc, k, in.AccountKeeper, in.BankKeeper, in.StakingKeeper, in.LegacySubspace)
return ModuleOutputs{
DistrKeeper: k,
Module: m,
Hooks: staking.StakingHooksWrapper{
StakingHooks: k.Hooks()
},
}
}
App Simulator manager
The following step is setting up theSimulatorManager
at the app level. This
is required for the simulation test files on the next step.
Copy
Ask AI
type CustomApp struct {
...
sm *module.SimulationManager
}
SimulationManager
instance in the same way we create the ModuleManager
but this time we only pass
the modules that implement the simulation functions from the AppModuleSimulation
interface described above.
Copy
Ask AI
func NewCustomApp(...) {
/ create the simulation manager and define the order of the modules for deterministic simulations
app.sm = module.NewSimulationManager(
auth.NewAppModule(app.accountKeeper),
bank.NewAppModule(app.bankKeeper, app.accountKeeper),
supply.NewAppModule(app.supplyKeeper, app.accountKeeper),
gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper),
mint.NewAppModule(app.mintKeeper),
distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper),
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper),
)
/ register the store decoders for simulation tests
app.sm.RegisterStoreDecoders()
...
}