github.com/Finschia/finschia-sdk@v0.49.1/x/staking/simulation/operations.go (about) 1 package simulation 2 3 import ( 4 "fmt" 5 "math/rand" 6 7 "github.com/Finschia/finschia-sdk/baseapp" 8 "github.com/Finschia/finschia-sdk/codec" 9 simappparams "github.com/Finschia/finschia-sdk/simapp/params" 10 sdk "github.com/Finschia/finschia-sdk/types" 11 simtypes "github.com/Finschia/finschia-sdk/types/simulation" 12 "github.com/Finschia/finschia-sdk/x/simulation" 13 "github.com/Finschia/finschia-sdk/x/staking/keeper" 14 "github.com/Finschia/finschia-sdk/x/staking/types" 15 ) 16 17 // Simulation operation weights constants 18 const ( 19 OpWeightMsgCreateValidator = "op_weight_msg_create_validator" 20 OpWeightMsgEditValidator = "op_weight_msg_edit_validator" 21 OpWeightMsgDelegate = "op_weight_msg_delegate" 22 OpWeightMsgUndelegate = "op_weight_msg_undelegate" 23 OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" 24 ) 25 26 // WeightedOperations returns all the operations from the module with their respective weights 27 func WeightedOperations( 28 appParams simtypes.AppParams, cdc codec.JSONCodec, ak types.AccountKeeper, 29 bk types.BankKeeper, k keeper.Keeper, 30 ) simulation.WeightedOperations { 31 var ( 32 weightMsgCreateValidator int 33 weightMsgEditValidator int 34 weightMsgDelegate int 35 weightMsgUndelegate int 36 weightMsgBeginRedelegate int 37 ) 38 39 appParams.GetOrGenerate(cdc, OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil, 40 func(_ *rand.Rand) { 41 weightMsgCreateValidator = simappparams.DefaultWeightMsgCreateValidator 42 }, 43 ) 44 45 appParams.GetOrGenerate(cdc, OpWeightMsgEditValidator, &weightMsgEditValidator, nil, 46 func(_ *rand.Rand) { 47 weightMsgEditValidator = simappparams.DefaultWeightMsgEditValidator 48 }, 49 ) 50 51 appParams.GetOrGenerate(cdc, OpWeightMsgDelegate, &weightMsgDelegate, nil, 52 func(_ *rand.Rand) { 53 weightMsgDelegate = simappparams.DefaultWeightMsgDelegate 54 }, 55 ) 56 57 appParams.GetOrGenerate(cdc, OpWeightMsgUndelegate, &weightMsgUndelegate, nil, 58 func(_ *rand.Rand) { 59 weightMsgUndelegate = simappparams.DefaultWeightMsgUndelegate 60 }, 61 ) 62 63 appParams.GetOrGenerate(cdc, OpWeightMsgBeginRedelegate, &weightMsgBeginRedelegate, nil, 64 func(_ *rand.Rand) { 65 weightMsgBeginRedelegate = simappparams.DefaultWeightMsgBeginRedelegate 66 }, 67 ) 68 69 return simulation.WeightedOperations{ 70 simulation.NewWeightedOperation( 71 weightMsgCreateValidator, 72 SimulateMsgCreateValidator(ak, bk, k), 73 ), 74 simulation.NewWeightedOperation( 75 weightMsgEditValidator, 76 SimulateMsgEditValidator(ak, bk, k), 77 ), 78 simulation.NewWeightedOperation( 79 weightMsgDelegate, 80 SimulateMsgDelegate(ak, bk, k), 81 ), 82 simulation.NewWeightedOperation( 83 weightMsgUndelegate, 84 SimulateMsgUndelegate(ak, bk, k), 85 ), 86 simulation.NewWeightedOperation( 87 weightMsgBeginRedelegate, 88 SimulateMsgBeginRedelegate(ak, bk, k), 89 ), 90 } 91 } 92 93 // SimulateMsgCreateValidator generates a MsgCreateValidator with random values 94 func SimulateMsgCreateValidator(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { 95 return func( 96 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 97 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 98 simAccount, _ := simtypes.RandomAcc(r, accs) 99 address := sdk.ValAddress(simAccount.Address) 100 101 // ensure the validator doesn't exist already 102 _, found := k.GetValidator(ctx, address) 103 if found { 104 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "unable to find validator"), nil, nil 105 } 106 107 denom := k.GetParams(ctx).BondDenom 108 109 balance := bk.GetBalance(ctx, simAccount.Address, denom).Amount 110 if !balance.IsPositive() { 111 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "balance is negative"), nil, nil 112 } 113 114 amount, err := simtypes.RandPositiveInt(r, balance) 115 if err != nil { 116 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "unable to generate positive amount"), nil, err 117 } 118 119 selfDelegation := sdk.NewCoin(denom, amount) 120 121 account := ak.GetAccount(ctx, simAccount.Address) 122 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 123 124 var fees sdk.Coins 125 126 coins, hasNeg := spendable.SafeSub(sdk.Coins{selfDelegation}) 127 if !hasNeg { 128 fees, err = simtypes.RandomFees(r, ctx, coins) 129 if err != nil { 130 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgCreateValidator, "unable to generate fees"), nil, err 131 } 132 } 133 134 description := types.NewDescription( 135 simtypes.RandStringOfLength(r, 10), 136 simtypes.RandStringOfLength(r, 10), 137 simtypes.RandStringOfLength(r, 10), 138 simtypes.RandStringOfLength(r, 10), 139 simtypes.RandStringOfLength(r, 10), 140 ) 141 142 maxCommission := sdk.NewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2) 143 commission := types.NewCommissionRates( 144 simtypes.RandomDecAmount(r, maxCommission), 145 maxCommission, 146 simtypes.RandomDecAmount(r, maxCommission), 147 ) 148 149 msg, err := types.NewMsgCreateValidator(address, simAccount.ConsKey.PubKey(), selfDelegation, description, commission, sdk.OneInt()) 150 if err != nil { 151 return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "unable to create CreateValidator message"), nil, err 152 } 153 154 txCtx := simulation.OperationInput{ 155 R: r, 156 App: app, 157 TxGen: simappparams.MakeTestEncodingConfig().TxConfig, 158 Cdc: nil, 159 Msg: msg, 160 MsgType: msg.Type(), 161 Context: ctx, 162 SimAccount: simAccount, 163 AccountKeeper: ak, 164 ModuleName: types.ModuleName, 165 } 166 167 return simulation.GenAndDeliverTx(txCtx, fees) 168 } 169 } 170 171 // SimulateMsgEditValidator generates a MsgEditValidator with random values 172 func SimulateMsgEditValidator(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { 173 return func( 174 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 175 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 176 if len(k.GetAllValidators(ctx)) == 0 { 177 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "number of validators equal zero"), nil, nil 178 } 179 180 val, ok := keeper.RandomValidator(r, k, ctx) 181 if !ok { 182 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "unable to pick a validator"), nil, nil 183 } 184 185 address := val.GetOperator() 186 187 newCommissionRate := simtypes.RandomDecAmount(r, val.Commission.MaxRate) 188 189 if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.BlockHeader().Time); err != nil { 190 // skip as the commission is invalid 191 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "invalid commission rate"), nil, nil 192 } 193 194 simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(val.GetOperator())) 195 if !found { 196 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgEditValidator, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator()) 197 } 198 199 account := ak.GetAccount(ctx, simAccount.Address) 200 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 201 202 description := types.NewDescription( 203 simtypes.RandStringOfLength(r, 10), 204 simtypes.RandStringOfLength(r, 10), 205 simtypes.RandStringOfLength(r, 10), 206 simtypes.RandStringOfLength(r, 10), 207 simtypes.RandStringOfLength(r, 10), 208 ) 209 210 msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil) 211 212 txCtx := simulation.OperationInput{ 213 R: r, 214 App: app, 215 TxGen: simappparams.MakeTestEncodingConfig().TxConfig, 216 Cdc: nil, 217 Msg: msg, 218 MsgType: msg.Type(), 219 Context: ctx, 220 SimAccount: simAccount, 221 AccountKeeper: ak, 222 Bankkeeper: bk, 223 ModuleName: types.ModuleName, 224 CoinsSpentInMsg: spendable, 225 } 226 227 return simulation.GenAndDeliverTxWithRandFees(txCtx) 228 } 229 } 230 231 // SimulateMsgDelegate generates a MsgDelegate with random values 232 func SimulateMsgDelegate(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { 233 return func( 234 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 235 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 236 denom := k.GetParams(ctx).BondDenom 237 238 if len(k.GetAllValidators(ctx)) == 0 { 239 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "number of validators equal zero"), nil, nil 240 } 241 242 simAccount, _ := simtypes.RandomAcc(r, accs) 243 val, ok := keeper.RandomValidator(r, k, ctx) 244 if !ok { 245 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "unable to pick a validator"), nil, nil 246 } 247 248 if val.InvalidExRate() { 249 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "validator's invalid echange rate"), nil, nil 250 } 251 252 amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount 253 if !amount.IsPositive() { 254 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "balance is negative"), nil, nil 255 } 256 257 amount, err := simtypes.RandPositiveInt(r, amount) 258 if err != nil { 259 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "unable to generate positive amount"), nil, err 260 } 261 262 bondAmt := sdk.NewCoin(denom, amount) 263 264 account := ak.GetAccount(ctx, simAccount.Address) 265 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 266 267 var fees sdk.Coins 268 269 coins, hasNeg := spendable.SafeSub(sdk.Coins{bondAmt}) 270 if !hasNeg { 271 fees, err = simtypes.RandomFees(r, ctx, coins) 272 if err != nil { 273 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDelegate, "unable to generate fees"), nil, err 274 } 275 } 276 277 msg := types.NewMsgDelegate(simAccount.Address, val.GetOperator(), bondAmt) 278 279 txCtx := simulation.OperationInput{ 280 R: r, 281 App: app, 282 TxGen: simappparams.MakeTestEncodingConfig().TxConfig, 283 Cdc: nil, 284 Msg: msg, 285 MsgType: msg.Type(), 286 Context: ctx, 287 SimAccount: simAccount, 288 AccountKeeper: ak, 289 ModuleName: types.ModuleName, 290 } 291 292 return simulation.GenAndDeliverTx(txCtx, fees) 293 } 294 } 295 296 // SimulateMsgUndelegate generates a MsgUndelegate with random values 297 func SimulateMsgUndelegate(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { 298 return func( 299 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 300 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 301 // get random validator 302 validator, ok := keeper.RandomValidator(r, k, ctx) 303 if !ok { 304 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "validator is not ok"), nil, nil 305 } 306 307 valAddr := validator.GetOperator() 308 delegations := k.GetValidatorDelegations(ctx, validator.GetOperator()) 309 if delegations == nil { 310 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "keeper does have any delegation entries"), nil, nil 311 } 312 313 // get random delegator from validator 314 delegation := delegations[r.Intn(len(delegations))] 315 delAddr := delegation.GetDelegatorAddr() 316 317 if k.HasMaxUnbondingDelegationEntries(ctx, delAddr, valAddr) { 318 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "keeper does have a max unbonding delegation entries"), nil, nil 319 } 320 321 totalBond := validator.TokensFromShares(delegation.GetShares()).TruncateInt() 322 if !totalBond.IsPositive() { 323 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "total bond is negative"), nil, nil 324 } 325 326 unbondAmt, err := simtypes.RandPositiveInt(r, totalBond) 327 if err != nil { 328 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "invalid unbond amount"), nil, err 329 } 330 331 if unbondAmt.IsZero() { 332 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgUndelegate, "unbond amount is zero"), nil, nil 333 } 334 335 msg := types.NewMsgUndelegate( 336 delAddr, valAddr, sdk.NewCoin(k.BondDenom(ctx), unbondAmt), 337 ) 338 339 // need to retrieve the simulation account associated with delegation to retrieve PrivKey 340 var simAccount simtypes.Account 341 342 for _, simAcc := range accs { 343 if simAcc.Address.Equals(delAddr) { 344 simAccount = simAcc 345 break 346 } 347 } 348 // if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error 349 if simAccount.PrivKey == nil { 350 return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr) 351 } 352 353 account := ak.GetAccount(ctx, delAddr) 354 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 355 356 txCtx := simulation.OperationInput{ 357 R: r, 358 App: app, 359 TxGen: simappparams.MakeTestEncodingConfig().TxConfig, 360 Cdc: nil, 361 Msg: msg, 362 MsgType: msg.Type(), 363 Context: ctx, 364 SimAccount: simAccount, 365 AccountKeeper: ak, 366 Bankkeeper: bk, 367 ModuleName: types.ModuleName, 368 CoinsSpentInMsg: spendable, 369 } 370 371 return simulation.GenAndDeliverTxWithRandFees(txCtx) 372 } 373 } 374 375 // SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values 376 func SimulateMsgBeginRedelegate(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { 377 return func( 378 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 379 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 380 // get random source validator 381 srcVal, ok := keeper.RandomValidator(r, k, ctx) 382 if !ok { 383 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "unable to pick validator"), nil, nil 384 } 385 386 srcAddr := srcVal.GetOperator() 387 delegations := k.GetValidatorDelegations(ctx, srcAddr) 388 if delegations == nil { 389 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "keeper does have any delegation entries"), nil, nil 390 } 391 392 // get random delegator from src validator 393 delegation := delegations[r.Intn(len(delegations))] 394 delAddr := delegation.GetDelegatorAddr() 395 396 if k.HasReceivingRedelegation(ctx, delAddr, srcAddr) { 397 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "receveing redelegation is not allowed"), nil, nil // skip 398 } 399 400 // get random destination validator 401 destVal, ok := keeper.RandomValidator(r, k, ctx) 402 if !ok { 403 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "unable to pick validator"), nil, nil 404 } 405 406 destAddr := destVal.GetOperator() 407 if srcAddr.Equals(destAddr) || destVal.InvalidExRate() || k.HasMaxRedelegationEntries(ctx, delAddr, srcAddr, destAddr) { 408 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "checks failed"), nil, nil 409 } 410 411 totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt() 412 if !totalBond.IsPositive() { 413 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "total bond is negative"), nil, nil 414 } 415 416 redAmt, err := simtypes.RandPositiveInt(r, totalBond) 417 if err != nil { 418 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "unable to generate positive amount"), nil, err 419 } 420 421 if redAmt.IsZero() { 422 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "amount is zero"), nil, nil 423 } 424 425 // check if the shares truncate to zero 426 shares, err := srcVal.SharesFromTokens(redAmt) 427 if err != nil { 428 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "invalid shares"), nil, err 429 } 430 431 if srcVal.TokensFromShares(shares).TruncateInt().IsZero() { 432 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "shares truncate to zero"), nil, nil // skip 433 } 434 435 // need to retrieve the simulation account associated with delegation to retrieve PrivKey 436 var simAccount simtypes.Account 437 438 for _, simAcc := range accs { 439 if simAcc.Address.Equals(delAddr) { 440 simAccount = simAcc 441 break 442 } 443 } 444 445 // if simaccount.PrivKey == nil, delegation address does not exist in accs. Return error 446 if simAccount.PrivKey == nil { 447 return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgBeginRedelegate, "account private key is nil"), nil, fmt.Errorf("delegation addr: %s does not exist in simulation accounts", delAddr) 448 } 449 450 account := ak.GetAccount(ctx, delAddr) 451 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 452 453 msg := types.NewMsgBeginRedelegate( 454 delAddr, srcAddr, destAddr, 455 sdk.NewCoin(k.BondDenom(ctx), redAmt), 456 ) 457 458 txCtx := simulation.OperationInput{ 459 R: r, 460 App: app, 461 TxGen: simappparams.MakeTestEncodingConfig().TxConfig, 462 Cdc: nil, 463 Msg: msg, 464 MsgType: msg.Type(), 465 Context: ctx, 466 SimAccount: simAccount, 467 AccountKeeper: ak, 468 Bankkeeper: bk, 469 ModuleName: types.ModuleName, 470 CoinsSpentInMsg: spendable, 471 } 472 473 return simulation.GenAndDeliverTxWithRandFees(txCtx) 474 } 475 }