github.com/cosmos/cosmos-sdk@v0.50.10/x/staking/simulation/operations.go (about) 1 package simulation 2 3 import ( 4 "bytes" 5 "fmt" 6 "math/rand" 7 8 "cosmossdk.io/math" 9 10 "github.com/cosmos/cosmos-sdk/baseapp" 11 "github.com/cosmos/cosmos-sdk/client" 12 "github.com/cosmos/cosmos-sdk/codec" 13 "github.com/cosmos/cosmos-sdk/testutil" 14 sdk "github.com/cosmos/cosmos-sdk/types" 15 simtypes "github.com/cosmos/cosmos-sdk/types/simulation" 16 "github.com/cosmos/cosmos-sdk/x/simulation" 17 "github.com/cosmos/cosmos-sdk/x/staking/keeper" 18 "github.com/cosmos/cosmos-sdk/x/staking/types" 19 ) 20 21 // Simulation operation weights constants 22 const ( 23 DefaultWeightMsgCreateValidator int = 100 24 DefaultWeightMsgEditValidator int = 5 25 DefaultWeightMsgDelegate int = 100 26 DefaultWeightMsgUndelegate int = 100 27 DefaultWeightMsgBeginRedelegate int = 100 28 DefaultWeightMsgCancelUnbondingDelegation int = 100 29 30 OpWeightMsgCreateValidator = "op_weight_msg_create_validator" 31 OpWeightMsgEditValidator = "op_weight_msg_edit_validator" 32 OpWeightMsgDelegate = "op_weight_msg_delegate" 33 OpWeightMsgUndelegate = "op_weight_msg_undelegate" 34 OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" 35 OpWeightMsgCancelUnbondingDelegation = "op_weight_msg_cancel_unbonding_delegation" 36 ) 37 38 // WeightedOperations returns all the operations from the module with their respective weights 39 func WeightedOperations( 40 appParams simtypes.AppParams, 41 cdc codec.JSONCodec, 42 txGen client.TxConfig, 43 ak types.AccountKeeper, 44 bk types.BankKeeper, 45 k *keeper.Keeper, 46 ) simulation.WeightedOperations { 47 var ( 48 weightMsgCreateValidator int 49 weightMsgEditValidator int 50 weightMsgDelegate int 51 weightMsgUndelegate int 52 weightMsgBeginRedelegate int 53 weightMsgCancelUnbondingDelegation int 54 ) 55 56 appParams.GetOrGenerate(OpWeightMsgCreateValidator, &weightMsgCreateValidator, nil, func(_ *rand.Rand) { 57 weightMsgCreateValidator = DefaultWeightMsgCreateValidator 58 }) 59 60 appParams.GetOrGenerate(OpWeightMsgEditValidator, &weightMsgEditValidator, nil, func(_ *rand.Rand) { 61 weightMsgEditValidator = DefaultWeightMsgEditValidator 62 }) 63 64 appParams.GetOrGenerate(OpWeightMsgDelegate, &weightMsgDelegate, nil, func(_ *rand.Rand) { 65 weightMsgDelegate = DefaultWeightMsgDelegate 66 }) 67 68 appParams.GetOrGenerate(OpWeightMsgUndelegate, &weightMsgUndelegate, nil, func(_ *rand.Rand) { 69 weightMsgUndelegate = DefaultWeightMsgUndelegate 70 }) 71 72 appParams.GetOrGenerate(OpWeightMsgBeginRedelegate, &weightMsgBeginRedelegate, nil, func(_ *rand.Rand) { 73 weightMsgBeginRedelegate = DefaultWeightMsgBeginRedelegate 74 }) 75 76 appParams.GetOrGenerate(OpWeightMsgCancelUnbondingDelegation, &weightMsgCancelUnbondingDelegation, nil, func(_ *rand.Rand) { 77 weightMsgCancelUnbondingDelegation = DefaultWeightMsgCancelUnbondingDelegation 78 }) 79 80 return simulation.WeightedOperations{ 81 simulation.NewWeightedOperation( 82 weightMsgCreateValidator, 83 SimulateMsgCreateValidator(txGen, ak, bk, k), 84 ), 85 simulation.NewWeightedOperation( 86 weightMsgEditValidator, 87 SimulateMsgEditValidator(txGen, ak, bk, k), 88 ), 89 simulation.NewWeightedOperation( 90 weightMsgDelegate, 91 SimulateMsgDelegate(txGen, ak, bk, k), 92 ), 93 simulation.NewWeightedOperation( 94 weightMsgUndelegate, 95 SimulateMsgUndelegate(txGen, ak, bk, k), 96 ), 97 simulation.NewWeightedOperation( 98 weightMsgBeginRedelegate, 99 SimulateMsgBeginRedelegate(txGen, ak, bk, k), 100 ), 101 simulation.NewWeightedOperation( 102 weightMsgCancelUnbondingDelegation, 103 SimulateMsgCancelUnbondingDelegate(txGen, ak, bk, k), 104 ), 105 } 106 } 107 108 // SimulateMsgCreateValidator generates a MsgCreateValidator with random values 109 func SimulateMsgCreateValidator( 110 txGen client.TxConfig, 111 ak types.AccountKeeper, 112 bk types.BankKeeper, 113 k *keeper.Keeper, 114 ) simtypes.Operation { 115 return func( 116 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 117 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 118 msgType := sdk.MsgTypeURL(&types.MsgCreateValidator{}) 119 120 simAccount, _ := simtypes.RandomAcc(r, accs) 121 address := sdk.ValAddress(simAccount.Address) 122 123 // ensure the validator doesn't exist already 124 _, err := k.GetValidator(ctx, address) 125 if err == nil { 126 return simtypes.NoOpMsg(types.ModuleName, msgType, "validator already exists"), nil, nil 127 } 128 129 denom, err := k.BondDenom(ctx) 130 if err != nil { 131 return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err 132 } 133 134 balance := bk.GetBalance(ctx, simAccount.Address, denom).Amount 135 if !balance.IsPositive() { 136 return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil 137 } 138 139 amount, err := simtypes.RandPositiveInt(r, balance) 140 if err != nil { 141 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err 142 } 143 144 selfDelegation := sdk.NewCoin(denom, amount) 145 146 account := ak.GetAccount(ctx, simAccount.Address) 147 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 148 149 var fees sdk.Coins 150 151 coins, hasNeg := spendable.SafeSub(selfDelegation) 152 if !hasNeg { 153 fees, err = simtypes.RandomFees(r, ctx, coins) 154 if err != nil { 155 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err 156 } 157 } 158 159 description := types.NewDescription( 160 simtypes.RandStringOfLength(r, 10), 161 simtypes.RandStringOfLength(r, 10), 162 simtypes.RandStringOfLength(r, 10), 163 simtypes.RandStringOfLength(r, 10), 164 simtypes.RandStringOfLength(r, 10), 165 ) 166 167 maxCommission := math.LegacyNewDecWithPrec(int64(simtypes.RandIntBetween(r, 0, 100)), 2) 168 commission := types.NewCommissionRates( 169 simtypes.RandomDecAmount(r, maxCommission), 170 maxCommission, 171 simtypes.RandomDecAmount(r, maxCommission), 172 ) 173 174 msg, err := types.NewMsgCreateValidator(address.String(), simAccount.ConsKey.PubKey(), selfDelegation, description, commission, math.OneInt()) 175 if err != nil { 176 return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to create CreateValidator message"), nil, err 177 } 178 179 txCtx := simulation.OperationInput{ 180 R: r, 181 App: app, 182 TxGen: txGen, 183 Cdc: nil, 184 Msg: msg, 185 Context: ctx, 186 SimAccount: simAccount, 187 AccountKeeper: ak, 188 ModuleName: types.ModuleName, 189 } 190 191 return simulation.GenAndDeliverTx(txCtx, fees) 192 } 193 } 194 195 // SimulateMsgEditValidator generates a MsgEditValidator with random values 196 func SimulateMsgEditValidator( 197 txGen client.TxConfig, 198 ak types.AccountKeeper, 199 bk types.BankKeeper, 200 k *keeper.Keeper, 201 ) simtypes.Operation { 202 return func( 203 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 204 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 205 msgType := sdk.MsgTypeURL(&types.MsgEditValidator{}) 206 207 vals, err := k.GetAllValidators(ctx) 208 if err != nil { 209 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err 210 } 211 212 if len(vals) == 0 { 213 return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil 214 } 215 216 val, ok := testutil.RandSliceElem(r, vals) 217 if !ok { 218 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil 219 } 220 221 address := val.GetOperator() 222 newCommissionRate := simtypes.RandomDecAmount(r, val.Commission.MaxRate) 223 224 if err := val.Commission.ValidateNewRate(newCommissionRate, ctx.BlockHeader().Time); err != nil { 225 // skip as the commission is invalid 226 return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid commission rate"), nil, nil 227 } 228 229 bz, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator()) 230 if err != nil { 231 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err 232 } 233 234 simAccount, found := simtypes.FindAccount(accs, sdk.AccAddress(bz)) 235 if !found { 236 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to find account"), nil, fmt.Errorf("validator %s not found", val.GetOperator()) 237 } 238 239 account := ak.GetAccount(ctx, simAccount.Address) 240 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 241 242 description := types.NewDescription( 243 simtypes.RandStringOfLength(r, 10), 244 simtypes.RandStringOfLength(r, 10), 245 simtypes.RandStringOfLength(r, 10), 246 simtypes.RandStringOfLength(r, 10), 247 simtypes.RandStringOfLength(r, 10), 248 ) 249 250 msg := types.NewMsgEditValidator(address, description, &newCommissionRate, nil) 251 252 txCtx := simulation.OperationInput{ 253 R: r, 254 App: app, 255 TxGen: txGen, 256 Cdc: nil, 257 Msg: msg, 258 Context: ctx, 259 SimAccount: simAccount, 260 AccountKeeper: ak, 261 Bankkeeper: bk, 262 ModuleName: types.ModuleName, 263 CoinsSpentInMsg: spendable, 264 } 265 266 return simulation.GenAndDeliverTxWithRandFees(txCtx) 267 } 268 } 269 270 // SimulateMsgDelegate generates a MsgDelegate with random values 271 func SimulateMsgDelegate( 272 txGen client.TxConfig, 273 ak types.AccountKeeper, 274 bk types.BankKeeper, 275 k *keeper.Keeper, 276 ) simtypes.Operation { 277 return func( 278 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 279 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 280 msgType := sdk.MsgTypeURL(&types.MsgDelegate{}) 281 denom, err := k.BondDenom(ctx) 282 if err != nil { 283 return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err 284 } 285 286 vals, err := k.GetAllValidators(ctx) 287 if err != nil { 288 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err 289 } 290 291 if len(vals) == 0 { 292 return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil 293 } 294 295 simAccount, _ := simtypes.RandomAcc(r, accs) 296 val, ok := testutil.RandSliceElem(r, vals) 297 if !ok { 298 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick a validator"), nil, nil 299 } 300 301 if val.InvalidExRate() { 302 return simtypes.NoOpMsg(types.ModuleName, msgType, "validator's invalid echange rate"), nil, nil 303 } 304 305 amount := bk.GetBalance(ctx, simAccount.Address, denom).Amount 306 if !amount.IsPositive() { 307 return simtypes.NoOpMsg(types.ModuleName, msgType, "balance is negative"), nil, nil 308 } 309 310 amount, err = simtypes.RandPositiveInt(r, amount) 311 if err != nil { 312 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err 313 } 314 315 bondAmt := sdk.NewCoin(denom, amount) 316 317 account := ak.GetAccount(ctx, simAccount.Address) 318 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 319 320 var fees sdk.Coins 321 322 coins, hasNeg := spendable.SafeSub(bondAmt) 323 if !hasNeg { 324 fees, err = simtypes.RandomFees(r, ctx, coins) 325 if err != nil { 326 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate fees"), nil, err 327 } 328 } 329 330 msg := types.NewMsgDelegate(simAccount.Address.String(), val.GetOperator(), bondAmt) 331 332 txCtx := simulation.OperationInput{ 333 R: r, 334 App: app, 335 TxGen: txGen, 336 Cdc: nil, 337 Msg: msg, 338 Context: ctx, 339 SimAccount: simAccount, 340 AccountKeeper: ak, 341 ModuleName: types.ModuleName, 342 } 343 344 return simulation.GenAndDeliverTx(txCtx, fees) 345 } 346 } 347 348 // SimulateMsgUndelegate generates a MsgUndelegate with random values 349 func SimulateMsgUndelegate( 350 txGen client.TxConfig, 351 ak types.AccountKeeper, 352 bk types.BankKeeper, 353 k *keeper.Keeper, 354 ) simtypes.Operation { 355 return func( 356 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 357 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 358 msgType := sdk.MsgTypeURL(&types.MsgUndelegate{}) 359 360 vals, err := k.GetAllValidators(ctx) 361 if err != nil { 362 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err 363 } 364 365 if len(vals) == 0 { 366 return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil 367 } 368 369 val, ok := testutil.RandSliceElem(r, vals) 370 if !ok { 371 return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil 372 } 373 374 valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator()) 375 if err != nil { 376 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err 377 } 378 delegations, err := k.GetValidatorDelegations(ctx, valAddr) 379 if err != nil { 380 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil 381 } 382 383 if delegations == nil { 384 return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil 385 } 386 387 // get random delegator from validator 388 delegation := delegations[r.Intn(len(delegations))] 389 delAddr := delegation.GetDelegatorAddr() 390 391 delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr) 392 if err != nil { 393 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err 394 } 395 396 hasMaxUD, err := k.HasMaxUnbondingDelegationEntries(ctx, delAddrBz, valAddr) 397 if err != nil { 398 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max unbonding delegation entries"), nil, err 399 } 400 401 if hasMaxUD { 402 return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have a max unbonding delegation entries"), nil, nil 403 } 404 405 totalBond := val.TokensFromShares(delegation.GetShares()).TruncateInt() 406 if !totalBond.IsPositive() { 407 return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil 408 } 409 410 unbondAmt, err := simtypes.RandPositiveInt(r, totalBond) 411 if err != nil { 412 return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid unbond amount"), nil, err 413 } 414 415 if unbondAmt.IsZero() { 416 return simtypes.NoOpMsg(types.ModuleName, msgType, "unbond amount is zero"), nil, nil 417 } 418 419 bondDenom, err := k.BondDenom(ctx) 420 if err != nil { 421 return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err 422 } 423 424 msg := types.NewMsgUndelegate( 425 delAddr, val.GetOperator(), sdk.NewCoin(bondDenom, unbondAmt), 426 ) 427 428 // need to retrieve the simulation account associated with delegation to retrieve PrivKey 429 var simAccount simtypes.Account 430 431 for _, simAcc := range accs { 432 if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) { 433 simAccount = simAcc 434 break 435 } 436 } 437 // 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 438 if simAccount.PrivKey == nil { 439 return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "account private key is nil"), nil, nil 440 } 441 442 account := ak.GetAccount(ctx, delAddrBz) 443 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 444 445 txCtx := simulation.OperationInput{ 446 R: r, 447 App: app, 448 TxGen: txGen, 449 Cdc: nil, 450 Msg: msg, 451 Context: ctx, 452 SimAccount: simAccount, 453 AccountKeeper: ak, 454 Bankkeeper: bk, 455 ModuleName: types.ModuleName, 456 CoinsSpentInMsg: spendable, 457 } 458 459 return simulation.GenAndDeliverTxWithRandFees(txCtx) 460 } 461 } 462 463 // SimulateMsgCancelUnbondingDelegate generates a MsgCancelUnbondingDelegate with random values 464 func SimulateMsgCancelUnbondingDelegate( 465 txGen client.TxConfig, 466 ak types.AccountKeeper, 467 bk types.BankKeeper, 468 k *keeper.Keeper, 469 ) simtypes.Operation { 470 return func( 471 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 472 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 473 msgType := sdk.MsgTypeURL(&types.MsgCancelUnbondingDelegation{}) 474 475 vals, err := k.GetAllValidators(ctx) 476 if err != nil { 477 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err 478 } 479 480 if len(vals) == 0 { 481 return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil 482 } 483 484 simAccount, _ := simtypes.RandomAcc(r, accs) 485 val, ok := testutil.RandSliceElem(r, vals) 486 if !ok { 487 return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is not ok"), nil, nil 488 } 489 490 if val.IsJailed() || val.InvalidExRate() { 491 return simtypes.NoOpMsg(types.ModuleName, msgType, "validator is jailed"), nil, nil 492 } 493 494 valAddr, err := k.ValidatorAddressCodec().StringToBytes(val.GetOperator()) 495 if err != nil { 496 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err 497 } 498 unbondingDelegation, err := k.GetUnbondingDelegation(ctx, simAccount.Address, valAddr) 499 if err != nil { 500 return simtypes.NoOpMsg(types.ModuleName, msgType, "account does have any unbonding delegation"), nil, nil 501 } 502 503 // This is a temporary fix to make staking simulation pass. We should fetch 504 // the first unbondingDelegationEntry that matches the creationHeight, because 505 // currently the staking msgServer chooses the first unbondingDelegationEntry 506 // with the matching creationHeight. 507 // 508 // ref: https://github.com/cosmos/cosmos-sdk/issues/12932 509 creationHeight := unbondingDelegation.Entries[r.Intn(len(unbondingDelegation.Entries))].CreationHeight 510 511 var unbondingDelegationEntry types.UnbondingDelegationEntry 512 513 for _, entry := range unbondingDelegation.Entries { 514 if entry.CreationHeight == creationHeight { 515 unbondingDelegationEntry = entry 516 break 517 } 518 } 519 520 if unbondingDelegationEntry.CompletionTime.Before(ctx.BlockTime()) { 521 return simtypes.NoOpMsg(types.ModuleName, msgType, "unbonding delegation is already processed"), nil, nil 522 } 523 524 if !unbondingDelegationEntry.Balance.IsPositive() { 525 return simtypes.NoOpMsg(types.ModuleName, msgType, "delegator receiving balance is negative"), nil, nil 526 } 527 528 cancelBondAmt := simtypes.RandomAmount(r, unbondingDelegationEntry.Balance) 529 530 if cancelBondAmt.IsZero() { 531 return simtypes.NoOpMsg(types.ModuleName, msgType, "cancelBondAmt amount is zero"), nil, nil 532 } 533 534 bondDenom, err := k.BondDenom(ctx) 535 if err != nil { 536 return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err 537 } 538 539 msg := types.NewMsgCancelUnbondingDelegation( 540 simAccount.Address.String(), val.GetOperator(), unbondingDelegationEntry.CreationHeight, sdk.NewCoin(bondDenom, cancelBondAmt), 541 ) 542 543 spendable := bk.SpendableCoins(ctx, simAccount.Address) 544 545 txCtx := simulation.OperationInput{ 546 R: r, 547 App: app, 548 TxGen: txGen, 549 Cdc: nil, 550 Msg: msg, 551 Context: ctx, 552 SimAccount: simAccount, 553 AccountKeeper: ak, 554 Bankkeeper: bk, 555 ModuleName: types.ModuleName, 556 CoinsSpentInMsg: spendable, 557 } 558 559 return simulation.GenAndDeliverTxWithRandFees(txCtx) 560 } 561 } 562 563 // SimulateMsgBeginRedelegate generates a MsgBeginRedelegate with random values 564 func SimulateMsgBeginRedelegate( 565 txGen client.TxConfig, 566 ak types.AccountKeeper, 567 bk types.BankKeeper, 568 k *keeper.Keeper, 569 ) simtypes.Operation { 570 return func( 571 r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, 572 ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { 573 msgType := sdk.MsgTypeURL(&types.MsgBeginRedelegate{}) 574 575 allVals, err := k.GetAllValidators(ctx) 576 if err != nil { 577 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to get validators"), nil, err 578 } 579 580 if len(allVals) == 0 { 581 return simtypes.NoOpMsg(types.ModuleName, msgType, "number of validators equal zero"), nil, nil 582 } 583 584 srcVal, ok := testutil.RandSliceElem(r, allVals) 585 if !ok { 586 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil 587 } 588 589 srcAddr, err := k.ValidatorAddressCodec().StringToBytes(srcVal.GetOperator()) 590 if err != nil { 591 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err 592 } 593 delegations, err := k.GetValidatorDelegations(ctx, srcAddr) 594 if err != nil { 595 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator delegations"), nil, nil 596 } 597 598 if delegations == nil { 599 return simtypes.NoOpMsg(types.ModuleName, msgType, "keeper does have any delegation entries"), nil, nil 600 } 601 602 // get random delegator from src validator 603 delegation := delegations[r.Intn(len(delegations))] 604 delAddr := delegation.GetDelegatorAddr() 605 606 delAddrBz, err := ak.AddressCodec().StringToBytes(delAddr) 607 if err != nil { 608 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting delegator address bytes"), nil, err 609 } 610 611 hasRecRedel, err := k.HasReceivingRedelegation(ctx, delAddrBz, srcAddr) 612 if err != nil { 613 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting receiving redelegation"), nil, err 614 } 615 616 if hasRecRedel { 617 return simtypes.NoOpMsg(types.ModuleName, msgType, "receveing redelegation is not allowed"), nil, nil // skip 618 } 619 620 // get random destination validator 621 destVal, ok := testutil.RandSliceElem(r, allVals) 622 if !ok { 623 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to pick validator"), nil, nil 624 } 625 626 destAddr, err := k.ValidatorAddressCodec().StringToBytes(destVal.GetOperator()) 627 if err != nil { 628 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting validator address bytes"), nil, err 629 } 630 hasMaxRedel, err := k.HasMaxRedelegationEntries(ctx, delAddrBz, srcAddr, destAddr) 631 if err != nil { 632 return simtypes.NoOpMsg(types.ModuleName, msgType, "error getting max redelegation entries"), nil, err 633 } 634 635 if bytes.Equal(srcAddr, destAddr) || destVal.InvalidExRate() || hasMaxRedel { 636 return simtypes.NoOpMsg(types.ModuleName, msgType, "checks failed"), nil, nil 637 } 638 639 totalBond := srcVal.TokensFromShares(delegation.GetShares()).TruncateInt() 640 if !totalBond.IsPositive() { 641 return simtypes.NoOpMsg(types.ModuleName, msgType, "total bond is negative"), nil, nil 642 } 643 644 redAmt, err := simtypes.RandPositiveInt(r, totalBond) 645 if err != nil { 646 return simtypes.NoOpMsg(types.ModuleName, msgType, "unable to generate positive amount"), nil, err 647 } 648 649 if redAmt.IsZero() { 650 return simtypes.NoOpMsg(types.ModuleName, msgType, "amount is zero"), nil, nil 651 } 652 653 // check if the shares truncate to zero 654 shares, err := srcVal.SharesFromTokens(redAmt) 655 if err != nil { 656 return simtypes.NoOpMsg(types.ModuleName, msgType, "invalid shares"), nil, err 657 } 658 659 if srcVal.TokensFromShares(shares).TruncateInt().IsZero() { 660 return simtypes.NoOpMsg(types.ModuleName, msgType, "shares truncate to zero"), nil, nil // skip 661 } 662 663 // need to retrieve the simulation account associated with delegation to retrieve PrivKey 664 var simAccount simtypes.Account 665 666 for _, simAcc := range accs { 667 if simAcc.Address.Equals(sdk.AccAddress(delAddrBz)) { 668 simAccount = simAcc 669 break 670 } 671 } 672 673 // 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 674 if simAccount.PrivKey == nil { 675 return simtypes.NoOpMsg(types.ModuleName, msgType, "account private key is nil"), nil, nil 676 } 677 678 account := ak.GetAccount(ctx, delAddrBz) 679 spendable := bk.SpendableCoins(ctx, account.GetAddress()) 680 681 bondDenom, err := k.BondDenom(ctx) 682 if err != nil { 683 return simtypes.NoOpMsg(types.ModuleName, msgType, "bond denom not found"), nil, err 684 } 685 686 msg := types.NewMsgBeginRedelegate( 687 delAddr, srcVal.GetOperator(), destVal.GetOperator(), 688 sdk.NewCoin(bondDenom, redAmt), 689 ) 690 691 txCtx := simulation.OperationInput{ 692 R: r, 693 App: app, 694 TxGen: txGen, 695 Cdc: nil, 696 Msg: msg, 697 Context: ctx, 698 SimAccount: simAccount, 699 AccountKeeper: ak, 700 Bankkeeper: bk, 701 ModuleName: types.ModuleName, 702 CoinsSpentInMsg: spendable, 703 } 704 705 return simulation.GenAndDeliverTxWithRandFees(txCtx) 706 } 707 }