feat(sims): Add simsx support to modules (#24145)
Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io>
This commit is contained in:
co-authored by
Alex | Interchain Labs
parent
99da3279be
commit
ea908cef68
@@ -1,8 +1,6 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
@@ -91,11 +89,5 @@ func RandomizedGenState(simState *module.SimulationState) {
|
||||
}
|
||||
|
||||
stakingGenesis := types.NewGenesisState(params, validators, delegations)
|
||||
|
||||
bz, err := json.MarshalIndent(&stakingGenesis.Params, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Selected randomly generated staking parameters:\n%s\n", bz)
|
||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(stakingGenesis)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
|
||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||
"github.com/cosmos/cosmos-sdk/simsx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
func MsgCreateValidatorFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgCreateValidator] {
|
||||
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgCreateValidator) {
|
||||
r := testData.Rand()
|
||||
withoutValidators := simsx.SimAccountFilterFn(func(a simsx.SimAccount) bool {
|
||||
_, err := k.GetValidator(ctx, sdk.ValAddress(a.Address))
|
||||
return err != nil
|
||||
})
|
||||
withoutConsAddrUsed := simsx.SimAccountFilterFn(func(a simsx.SimAccount) bool {
|
||||
consPubKey := sdk.GetConsAddress(a.ConsKey.PubKey())
|
||||
_, err := k.GetValidatorByConsAddr(ctx, consPubKey)
|
||||
return err != nil
|
||||
})
|
||||
bondDenom := must(k.BondDenom(ctx))
|
||||
valOper := testData.AnyAccount(reporter, withoutValidators, withoutConsAddrUsed, simsx.WithDenomBalance(bondDenom))
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newPubKey := valOper.ConsKey.PubKey()
|
||||
assertKeyUnused(ctx, reporter, k, newPubKey)
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
selfDelegation := valOper.LiquidBalance().RandSubsetCoin(reporter, bondDenom)
|
||||
|
||||
description := types.NewDescription(
|
||||
r.StringN(10),
|
||||
r.StringN(10),
|
||||
r.StringN(10),
|
||||
r.StringN(10),
|
||||
r.StringN(10),
|
||||
)
|
||||
|
||||
maxCommission := math.LegacyNewDecWithPrec(int64(r.IntInRange(0, 100)), 2)
|
||||
commission := types.NewCommissionRates(
|
||||
r.DecN(maxCommission),
|
||||
maxCommission,
|
||||
r.DecN(maxCommission),
|
||||
)
|
||||
|
||||
addr := must(k.ValidatorAddressCodec().BytesToString(valOper.Address))
|
||||
msg, err := types.NewMsgCreateValidator(addr, newPubKey, selfDelegation, description, commission, math.OneInt())
|
||||
if err != nil {
|
||||
reporter.Skip(err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return []simsx.SimAccount{valOper}, msg
|
||||
}
|
||||
}
|
||||
|
||||
func MsgDelegateFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgDelegate] {
|
||||
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgDelegate) {
|
||||
r := testData.Rand()
|
||||
bondDenom := must(k.BondDenom(ctx))
|
||||
val := randomValidator(ctx, reporter, k, r)
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if val.InvalidExRate() {
|
||||
reporter.Skip("validator's invalid exchange rate")
|
||||
return nil, nil
|
||||
}
|
||||
sender := testData.AnyAccount(reporter)
|
||||
delegation := sender.LiquidBalance().RandSubsetCoin(reporter, bondDenom)
|
||||
return []simsx.SimAccount{sender}, types.NewMsgDelegate(sender.AddressBech32, val.GetOperator(), delegation)
|
||||
}
|
||||
}
|
||||
|
||||
func MsgUndelegateFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgUndelegate] {
|
||||
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUndelegate) {
|
||||
r := testData.Rand()
|
||||
bondDenom := must(k.BondDenom(ctx))
|
||||
val := randomValidator(ctx, reporter, k, r)
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// select delegator and amount for undelegate
|
||||
valAddr := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator()))
|
||||
delegations := must(k.GetValidatorDelegations(ctx, valAddr))
|
||||
if delegations == nil {
|
||||
reporter.Skip("no delegation entries")
|
||||
return nil, nil
|
||||
}
|
||||
// get random delegator from validator
|
||||
delegation := delegations[r.Intn(len(delegations))]
|
||||
delAddr := delegation.GetDelegatorAddr()
|
||||
delegator := testData.GetAccount(reporter, delAddr)
|
||||
|
||||
if hasMaxUD := must(k.HasMaxUnbondingDelegationEntries(ctx, delegator.Address, valAddr)); hasMaxUD {
|
||||
reporter.Skipf("max unbodings")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt()
|
||||
if !totalBond.IsPositive() {
|
||||
reporter.Skip("total bond is negative")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
unbondAmt := must(r.PositiveSDKIntn(totalBond))
|
||||
msg := types.NewMsgUndelegate(delAddr, val.GetOperator(), sdk.NewCoin(bondDenom, unbondAmt))
|
||||
return []simsx.SimAccount{delegator}, msg
|
||||
}
|
||||
}
|
||||
|
||||
func MsgEditValidatorFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgEditValidator] {
|
||||
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgEditValidator) {
|
||||
r := testData.Rand()
|
||||
val := randomValidator(ctx, reporter, k, r)
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
newCommissionRate := r.DecN(val.Commission.MaxRate)
|
||||
if err := val.Commission.ValidateNewRate(newCommissionRate, simsx.BlockTime(ctx)); err != nil {
|
||||
// skip as the commission is invalid
|
||||
reporter.Skip("invalid commission rate")
|
||||
return nil, nil
|
||||
}
|
||||
valOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator()))
|
||||
valOper := testData.GetAccountbyAccAddr(reporter, valOpAddrBz)
|
||||
d := types.NewDescription(r.StringN(10), r.StringN(10), r.StringN(10), r.StringN(10), r.StringN(10))
|
||||
|
||||
msg := types.NewMsgEditValidator(val.GetOperator(), d, &newCommissionRate, nil)
|
||||
return []simsx.SimAccount{valOper}, msg
|
||||
}
|
||||
}
|
||||
|
||||
func MsgBeginRedelegateFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgBeginRedelegate] {
|
||||
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgBeginRedelegate) {
|
||||
bondDenom := must(k.BondDenom(ctx))
|
||||
if !testData.IsSendEnabledDenom(bondDenom) {
|
||||
reporter.Skip("bond denom send not enabled")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r := testData.Rand()
|
||||
// select random validator as src
|
||||
vals := must(k.GetAllValidators(ctx))
|
||||
if len(vals) < 2 {
|
||||
reporter.Skip("insufficient number of validators")
|
||||
return nil, nil
|
||||
}
|
||||
srcVal := simsx.OneOf(r, vals)
|
||||
srcValOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(srcVal.GetOperator()))
|
||||
delegations := must(k.GetValidatorDelegations(ctx, srcValOpAddrBz))
|
||||
if delegations == nil {
|
||||
reporter.Skip("no delegations")
|
||||
return nil, nil
|
||||
}
|
||||
// get random delegator from src validator
|
||||
delegation := simsx.OneOf(r, delegations)
|
||||
totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt()
|
||||
if !totalBond.IsPositive() {
|
||||
reporter.Skip("total bond is negative")
|
||||
return nil, nil
|
||||
}
|
||||
redAmount, err := r.PositiveSDKIntn(totalBond)
|
||||
if err != nil || redAmount.IsZero() {
|
||||
reporter.Skip("unable to generate positive amount")
|
||||
return nil, nil
|
||||
}
|
||||
if totalBond.Sub(redAmount).IsZero() {
|
||||
reporter.Skip("can not redelegate all")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// check if the shares truncate to zero
|
||||
shares := must(srcVal.SharesFromTokens(redAmount))
|
||||
if srcVal.TokensFromShares(shares).TruncateInt().IsZero() {
|
||||
reporter.Skip("shares truncate to zero")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// pick a random delegator
|
||||
delAddr := delegation.GetDelegatorAddr()
|
||||
delAddrBz := must(testData.AddressCodec().StringToBytes(delAddr))
|
||||
if hasRecRedel := must(k.HasReceivingRedelegation(ctx, delAddrBz, srcValOpAddrBz)); hasRecRedel {
|
||||
reporter.Skip("receiving redelegation is not allowed")
|
||||
return nil, nil
|
||||
}
|
||||
delegator := testData.GetAccountbyAccAddr(reporter, delAddrBz)
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// get random destination validator
|
||||
destVal := simsx.OneOf(r, vals)
|
||||
if srcVal.Equal(&destVal) {
|
||||
destVal = simsx.OneOf(r, slices.DeleteFunc(vals, func(v types.Validator) bool { return srcVal.Equal(&v) }))
|
||||
}
|
||||
if destVal.InvalidExRate() {
|
||||
reporter.Skip("invalid delegation rate")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
destAddrBz := must(k.ValidatorAddressCodec().StringToBytes(destVal.GetOperator()))
|
||||
if hasMaxRedel := must(k.HasMaxRedelegationEntries(ctx, delAddrBz, srcValOpAddrBz, destAddrBz)); hasMaxRedel {
|
||||
reporter.Skip("maximum redelegation entries reached")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
msg := types.NewMsgBeginRedelegate(
|
||||
delAddr, srcVal.GetOperator(), destVal.GetOperator(),
|
||||
sdk.NewCoin(bondDenom, redAmount),
|
||||
)
|
||||
return []simsx.SimAccount{delegator}, msg
|
||||
}
|
||||
}
|
||||
|
||||
func MsgCancelUnbondingDelegationFactory(k *keeper.Keeper) simsx.SimMsgFactoryFn[*types.MsgCancelUnbondingDelegation] {
|
||||
return func(ctx context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgCancelUnbondingDelegation) {
|
||||
r := testData.Rand()
|
||||
val := randomValidator(ctx, reporter, k, r)
|
||||
if reporter.IsSkipped() {
|
||||
return nil, nil
|
||||
}
|
||||
if val.IsJailed() || val.InvalidExRate() {
|
||||
reporter.Skip("validator is jailed")
|
||||
return nil, nil
|
||||
}
|
||||
valOpAddrBz := must(k.ValidatorAddressCodec().StringToBytes(val.GetOperator()))
|
||||
valOper := testData.GetAccountbyAccAddr(reporter, valOpAddrBz)
|
||||
unbondingDelegation, err := k.GetUnbondingDelegation(ctx, valOper.Address, valOpAddrBz)
|
||||
if err != nil {
|
||||
reporter.Skip("no unbonding delegation")
|
||||
return 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(simsx.BlockTime(ctx)) {
|
||||
reporter.Skip("unbonding delegation is already processed")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !unbondingDelegationEntry.Balance.IsPositive() {
|
||||
reporter.Skip("delegator receiving balance is negative")
|
||||
return nil, nil
|
||||
}
|
||||
cancelBondAmt := r.Amount(unbondingDelegationEntry.Balance)
|
||||
if cancelBondAmt.IsZero() {
|
||||
reporter.Skip("cancelBondAmt amount is zero")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
msg := types.NewMsgCancelUnbondingDelegation(
|
||||
valOper.AddressBech32,
|
||||
val.GetOperator(),
|
||||
unbondingDelegationEntry.CreationHeight,
|
||||
sdk.NewCoin(must(k.BondDenom(ctx)), cancelBondAmt),
|
||||
)
|
||||
|
||||
return []simsx.SimAccount{valOper}, msg
|
||||
}
|
||||
}
|
||||
|
||||
// MsgUpdateParamsFactory creates a gov proposal for param updates
|
||||
func MsgUpdateParamsFactory() simsx.SimMsgFactoryFn[*types.MsgUpdateParams] {
|
||||
return func(_ context.Context, testData *simsx.ChainDataSource, reporter simsx.SimulationReporter) ([]simsx.SimAccount, *types.MsgUpdateParams) {
|
||||
r := testData.Rand()
|
||||
params := types.DefaultParams()
|
||||
// do not modify denom or staking will break
|
||||
params.HistoricalEntries = r.Uint32InRange(0, 1000)
|
||||
params.MaxEntries = r.Uint32InRange(1, 1000)
|
||||
params.MaxValidators = r.Uint32InRange(1, 1000)
|
||||
params.UnbondingTime = time.Duration(r.Timestamp().UnixNano())
|
||||
// modifying commission rate can cause issues for proposals within the same block
|
||||
// params.MinCommissionRate = r.DecN(sdkmath.LegacyNewDec(1))
|
||||
|
||||
return nil, &types.MsgUpdateParams{
|
||||
Authority: testData.ModuleAccountAddress(reporter, "gov"),
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func randomValidator(ctx context.Context, reporter simsx.SimulationReporter, k *keeper.Keeper, r *simsx.XRand) types.Validator {
|
||||
vals, err := k.GetAllValidators(ctx)
|
||||
if err != nil || len(vals) == 0 {
|
||||
reporter.Skipf("unable to get validators or empty list: %s", err)
|
||||
return types.Validator{}
|
||||
}
|
||||
return simsx.OneOf(r, vals)
|
||||
}
|
||||
|
||||
// skips execution if there's another key rotation for the same key in the same block
|
||||
func assertKeyUnused(ctx context.Context, reporter simsx.SimulationReporter, k *keeper.Keeper, newPubKey cryptotypes.PubKey) {
|
||||
newConsAddr := sdk.ConsAddress(newPubKey.Address())
|
||||
if _, err := k.GetValidatorByConsAddr(ctx, newConsAddr); err == nil {
|
||||
reporter.Skip("cons key already used")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func must[T any](r T, err error) T {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
)
|
||||
|
||||
// Simulation operation weights constants
|
||||
// will be removed in the future
|
||||
const (
|
||||
DefaultWeightMsgCreateValidator int = 100
|
||||
DefaultWeightMsgEditValidator int = 5
|
||||
@@ -36,6 +37,7 @@ const (
|
||||
)
|
||||
|
||||
// WeightedOperations returns all the operations from the module with their respective weights
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func WeightedOperations(
|
||||
appParams simtypes.AppParams,
|
||||
cdc codec.JSONCodec,
|
||||
@@ -106,6 +108,7 @@ func WeightedOperations(
|
||||
}
|
||||
|
||||
// SimulateMsgCreateValidator generates a MsgCreateValidator with random values
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgCreateValidator(
|
||||
txGen client.TxConfig,
|
||||
ak types.AccountKeeper,
|
||||
@@ -193,6 +196,7 @@ func SimulateMsgCreateValidator(
|
||||
}
|
||||
|
||||
// SimulateMsgEditValidator generates a MsgEditValidator with random values
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgEditValidator(
|
||||
txGen client.TxConfig,
|
||||
ak types.AccountKeeper,
|
||||
@@ -268,6 +272,7 @@ func SimulateMsgEditValidator(
|
||||
}
|
||||
|
||||
// SimulateMsgDelegate generates a MsgDelegate with random values
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgDelegate(
|
||||
txGen client.TxConfig,
|
||||
ak types.AccountKeeper,
|
||||
@@ -346,6 +351,7 @@ func SimulateMsgDelegate(
|
||||
}
|
||||
|
||||
// SimulateMsgUndelegate generates a MsgUndelegate with random values
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgUndelegate(
|
||||
txGen client.TxConfig,
|
||||
ak types.AccountKeeper,
|
||||
@@ -461,6 +467,7 @@ func SimulateMsgUndelegate(
|
||||
}
|
||||
|
||||
// SimulateMsgCancelUnbondingDelegate generates a MsgCancelUnbondingDelegate with random values
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgCancelUnbondingDelegate(
|
||||
txGen client.TxConfig,
|
||||
ak types.AccountKeeper,
|
||||
@@ -561,6 +568,7 @@ func SimulateMsgCancelUnbondingDelegate(
|
||||
}
|
||||
|
||||
// SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgBeginRedelegate(
|
||||
txGen client.TxConfig,
|
||||
ak types.AccountKeeper,
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
// Simulation operation weights constants
|
||||
// will be removed in the future
|
||||
const (
|
||||
DefaultWeightMsgUpdateParams int = 100
|
||||
|
||||
@@ -21,6 +22,7 @@ const (
|
||||
)
|
||||
|
||||
// ProposalMsgs defines the module weighted proposals' contents
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func ProposalMsgs() []simtypes.WeightedProposalMsg {
|
||||
return []simtypes.WeightedProposalMsg{
|
||||
simulation.NewWeightedProposalMsg(
|
||||
@@ -32,6 +34,7 @@ func ProposalMsgs() []simtypes.WeightedProposalMsg {
|
||||
}
|
||||
|
||||
// SimulateMsgUpdateParams returns a random MsgUpdateParams
|
||||
// migrate to the msg factories instead, this method will be removed in the future
|
||||
func SimulateMsgUpdateParams(r *rand.Rand, _ sdk.Context, _ []simtypes.Account) sdk.Msg {
|
||||
// use the default gov module account address as authority
|
||||
var authority sdk.AccAddress = address.Module("gov")
|
||||
|
||||
Reference in New Issue
Block a user