753 lines
23 KiB
Go
753 lines
23 KiB
Go
package simulation
|
|
|
|
import (
|
|
"math"
|
|
"math/rand"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
sdkmath "cosmossdk.io/math"
|
|
"cosmossdk.io/x/gov/keeper"
|
|
"cosmossdk.io/x/gov/types"
|
|
v1 "cosmossdk.io/x/gov/types/v1"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
|
"github.com/cosmos/cosmos-sdk/x/simulation"
|
|
)
|
|
|
|
const unsetProposalID = 100000000000000
|
|
|
|
// Governance message types and routes
|
|
var (
|
|
TypeMsgDeposit = sdk.MsgTypeURL(&v1.MsgDeposit{})
|
|
TypeMsgVote = sdk.MsgTypeURL(&v1.MsgVote{})
|
|
TypeMsgVoteWeighted = sdk.MsgTypeURL(&v1.MsgVoteWeighted{})
|
|
TypeMsgSubmitProposal = sdk.MsgTypeURL(&v1.MsgSubmitProposal{})
|
|
TypeMsgCancelProposal = sdk.MsgTypeURL(&v1.MsgCancelProposal{})
|
|
)
|
|
|
|
// Simulation operation weights constants
|
|
const (
|
|
OpWeightMsgDeposit = "op_weight_msg_deposit"
|
|
OpWeightMsgVote = "op_weight_msg_vote"
|
|
OpWeightMsgVoteWeighted = "op_weight_msg_weighted_vote"
|
|
OpWeightMsgCancelProposal = "op_weight_msg_cancel_proposal"
|
|
|
|
DefaultWeightMsgDeposit = 100
|
|
DefaultWeightMsgVote = 67
|
|
DefaultWeightMsgVoteWeighted = 33
|
|
DefaultWeightTextProposal = 5
|
|
DefaultWeightMsgCancelProposal = 5
|
|
)
|
|
|
|
// SharedState shared state between message invocations
|
|
type SharedState struct {
|
|
minProposalID atomic.Uint64
|
|
}
|
|
|
|
// NewSharedState constructor
|
|
func NewSharedState() *SharedState {
|
|
r := &SharedState{}
|
|
r.setMinProposalID(unsetProposalID)
|
|
return r
|
|
}
|
|
|
|
func (s *SharedState) getMinProposalID() uint64 {
|
|
return s.minProposalID.Load()
|
|
}
|
|
|
|
func (s *SharedState) setMinProposalID(id uint64) {
|
|
s.minProposalID.Store(id)
|
|
}
|
|
|
|
// WeightedOperations returns all the operations from the module with their respective weights
|
|
func WeightedOperations(
|
|
appParams simtypes.AppParams,
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
wMsgs []simtypes.WeightedProposalMsg,
|
|
wContents []simtypes.WeightedProposalContent, //nolint:staticcheck // used for legacy testing
|
|
) simulation.WeightedOperations {
|
|
var (
|
|
weightMsgDeposit int
|
|
weightMsgVote int
|
|
weightMsgVoteWeighted int
|
|
weightMsgCancelProposal int
|
|
)
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgDeposit, &weightMsgDeposit, nil,
|
|
func(_ *rand.Rand) {
|
|
weightMsgDeposit = DefaultWeightMsgDeposit
|
|
},
|
|
)
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgVote, &weightMsgVote, nil,
|
|
func(_ *rand.Rand) {
|
|
weightMsgVote = DefaultWeightMsgVote
|
|
},
|
|
)
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgVoteWeighted, &weightMsgVoteWeighted, nil,
|
|
func(_ *rand.Rand) {
|
|
weightMsgVoteWeighted = DefaultWeightMsgVoteWeighted
|
|
},
|
|
)
|
|
|
|
appParams.GetOrGenerate(OpWeightMsgCancelProposal, &weightMsgCancelProposal, nil,
|
|
func(_ *rand.Rand) {
|
|
weightMsgCancelProposal = DefaultWeightMsgCancelProposal
|
|
},
|
|
)
|
|
|
|
// generate the weighted operations for the proposal msgs
|
|
var wProposalOps simulation.WeightedOperations
|
|
for _, wMsg := range wMsgs {
|
|
wMsg := wMsg // pin variable
|
|
var weight int
|
|
appParams.GetOrGenerate(wMsg.AppParamsKey(), &weight, nil,
|
|
func(_ *rand.Rand) { weight = wMsg.DefaultWeight() },
|
|
)
|
|
|
|
wProposalOps = append(
|
|
wProposalOps,
|
|
simulation.NewWeightedOperation(
|
|
weight,
|
|
SimulateMsgSubmitProposal(txGen, ak, bk, k, wMsg.MsgSimulatorFn()),
|
|
),
|
|
)
|
|
}
|
|
|
|
// generate the weighted operations for the proposal contents
|
|
var wLegacyProposalOps simulation.WeightedOperations
|
|
for _, wContent := range wContents {
|
|
wContent := wContent // pin variable
|
|
var weight int
|
|
appParams.GetOrGenerate(wContent.AppParamsKey(), &weight, nil,
|
|
func(_ *rand.Rand) { weight = wContent.DefaultWeight() },
|
|
)
|
|
|
|
wLegacyProposalOps = append(
|
|
wLegacyProposalOps,
|
|
simulation.NewWeightedOperation(
|
|
weight,
|
|
SimulateMsgSubmitLegacyProposal(txGen, ak, bk, k, wContent.ContentSimulatorFn()),
|
|
),
|
|
)
|
|
}
|
|
state := NewSharedState()
|
|
wGovOps := simulation.WeightedOperations{
|
|
simulation.NewWeightedOperation(
|
|
weightMsgDeposit,
|
|
SimulateMsgDeposit(txGen, ak, bk, k, state),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgVote,
|
|
SimulateMsgVote(txGen, ak, bk, k, state),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgVoteWeighted,
|
|
SimulateMsgVoteWeighted(txGen, ak, bk, k, state),
|
|
),
|
|
simulation.NewWeightedOperation(
|
|
weightMsgCancelProposal,
|
|
SimulateMsgCancelProposal(txGen, ak, bk, k),
|
|
),
|
|
}
|
|
|
|
return append(wGovOps, append(wProposalOps, wLegacyProposalOps...)...)
|
|
}
|
|
|
|
// SimulateMsgSubmitProposal simulates creating a msg Submit Proposal
|
|
// voting on the proposal, and subsequently slashing the proposal. It is implemented using
|
|
// future operations.
|
|
func SimulateMsgSubmitProposal(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
msgSim simtypes.MsgSimulatorFnX,
|
|
) simtypes.Operation {
|
|
return func(r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
msgs := []sdk.Msg{}
|
|
proposalMsg, err := msgSim(ctx, r, accs, ak.AddressCodec())
|
|
if err != nil {
|
|
return simtypes.OperationMsg{}, nil, err
|
|
}
|
|
if proposalMsg != nil {
|
|
msgs = append(msgs, proposalMsg)
|
|
}
|
|
|
|
return simulateMsgSubmitProposal(txGen, ak, bk, k, msgs)(r, app, ctx, accs, chainID)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgSubmitLegacyProposal simulates creating a msg Submit Proposal
|
|
// voting on the proposal, and subsequently slashing the proposal. It is implemented using
|
|
// future operations.
|
|
func SimulateMsgSubmitLegacyProposal(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
contentSim simtypes.ContentSimulatorFn, //nolint:staticcheck // used for legacy testing
|
|
) simtypes.Operation {
|
|
return func(r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
// 1) submit proposal now
|
|
content := contentSim(r, ctx, accs)
|
|
if content == nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "content is nil"), nil, nil
|
|
}
|
|
|
|
govacc, err := ak.AddressCodec().BytesToString(k.GetGovernanceAccount(ctx).GetAddress())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error getting governance account address"), nil, err
|
|
}
|
|
contentMsg, err := v1.NewLegacyContent(content, govacc)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error converting legacy content into proposal message"), nil, err
|
|
}
|
|
|
|
return simulateMsgSubmitProposal(txGen, ak, bk, k, []sdk.Msg{contentMsg})(r, app, ctx, accs, chainID)
|
|
}
|
|
}
|
|
|
|
func simulateMsgSubmitProposal(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
proposalMsgs []sdk.Msg,
|
|
) simtypes.Operation {
|
|
// The states are:
|
|
// column 1: All validators vote
|
|
// column 2: 90% vote
|
|
// column 3: 75% vote
|
|
// column 4: 40% vote
|
|
// column 5: 15% vote
|
|
// column 6: no one votes
|
|
// All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily,
|
|
// feel free to change.
|
|
numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{
|
|
{20, 10, 0, 0, 0, 0},
|
|
{55, 50, 20, 10, 0, 0},
|
|
{25, 25, 30, 25, 30, 15},
|
|
{0, 15, 30, 25, 30, 30},
|
|
{0, 0, 20, 30, 30, 30},
|
|
{0, 0, 0, 10, 10, 25},
|
|
})
|
|
|
|
statePercentageArray := []float64{1, .9, .75, .4, .15, 0}
|
|
curNumVotesState := 1
|
|
|
|
return func(
|
|
r *rand.Rand,
|
|
app simtypes.AppEntrypoint,
|
|
ctx sdk.Context,
|
|
accs []simtypes.Account,
|
|
chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
simAccount, _ := simtypes.RandomAcc(r, accs)
|
|
expedited := r.Intn(2) == 0
|
|
deposit, skip, err := randomDeposit(r, ctx, ak, bk, k, simAccount.Address, true, expedited)
|
|
switch {
|
|
case skip:
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "skip deposit"), nil, nil
|
|
case err != nil:
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "unable to generate deposit"), nil, err
|
|
}
|
|
|
|
proposalType := v1.ProposalType_PROPOSAL_TYPE_STANDARD
|
|
if expedited {
|
|
proposalType = v1.ProposalType_PROPOSAL_TYPE_EXPEDITED
|
|
}
|
|
|
|
accAddr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgSubmitProposal, "error getting simAccount address"), nil, err
|
|
}
|
|
msg, err := v1.NewMsgSubmitProposal(
|
|
proposalMsgs,
|
|
deposit,
|
|
accAddr,
|
|
simtypes.RandStringOfLength(r, 100),
|
|
simtypes.RandStringOfLength(r, 100),
|
|
simtypes.RandStringOfLength(r, 100),
|
|
proposalType,
|
|
)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate a submit proposal msg"), nil, err
|
|
}
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
tx, err := simtestutil.GenSignedMockTx(
|
|
r,
|
|
txGen,
|
|
[]sdk.Msg{msg},
|
|
sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 0)},
|
|
simtestutil.DefaultGenTxGas,
|
|
chainID,
|
|
[]uint64{account.GetAccountNumber()},
|
|
[]uint64{account.GetSequence()},
|
|
simAccount.PrivKey,
|
|
)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate mock tx"), nil, err
|
|
}
|
|
|
|
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err
|
|
}
|
|
|
|
opMsg := simtypes.NewOperationMsg(msg, true, "")
|
|
|
|
// get the submitted proposal ID
|
|
proposalID, err := k.ProposalID.Peek(ctx)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate proposalID"), nil, err
|
|
}
|
|
|
|
// 2) Schedule operations for votes
|
|
// 2.1) first pick a number of people to vote.
|
|
curNumVotesState = numVotesTransitionMatrix.NextState(r, curNumVotesState)
|
|
numVotes := int(math.Ceil(float64(len(accs)) * statePercentageArray[curNumVotesState]))
|
|
|
|
// 2.2) select who votes and when
|
|
whoVotes := r.Perm(len(accs))
|
|
|
|
// didntVote := whoVotes[numVotes:]
|
|
whoVotes = whoVotes[:numVotes]
|
|
params, _ := k.Params.Get(ctx)
|
|
votingPeriod := params.VotingPeriod
|
|
var fops []simtypes.FutureOperation
|
|
if false { // future ops deactivated because they were not implemented correct in the framework before and flood the system now
|
|
fops = make([]simtypes.FutureOperation, numVotes+1)
|
|
for i := 0; i < numVotes; i++ {
|
|
whenVote := ctx.HeaderInfo().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second)
|
|
fops[i] = simtypes.FutureOperation{
|
|
BlockTime: whenVote,
|
|
Op: func(r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accounts []simtypes.Account, chainID string) (OperationMsg simtypes.OperationMsg, futureOps []simtypes.FutureOperation, err error) {
|
|
return operationSimulateMsgVote(txGen, ak, bk, k, accs[whoVotes[i]], int64(proposalID), nil)(r, app, ctx, accounts, chainID)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
return opMsg, fops, nil
|
|
}
|
|
}
|
|
|
|
// SimulateMsgDeposit generates a MsgDeposit with random values.
|
|
func SimulateMsgDeposit(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
s *SharedState,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context,
|
|
accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
simAccount, _ := simtypes.RandomAcc(r, accs)
|
|
proposalID, ok := randomProposalID(r, k, ctx, v1.StatusDepositPeriod, s)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate proposalID"), nil, nil
|
|
}
|
|
|
|
p, err := k.Proposals.Get(ctx, proposalID)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to get proposal"), nil, err
|
|
}
|
|
|
|
isExpedited := p.ProposalType == v1.ProposalType_PROPOSAL_TYPE_EXPEDITED
|
|
|
|
deposit, skip, err := randomDeposit(r, ctx, ak, bk, k, simAccount.Address, false, isExpedited)
|
|
switch {
|
|
case skip:
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "skip deposit"), nil, nil
|
|
case err != nil:
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate deposit"), nil, err
|
|
}
|
|
|
|
addr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to get simAccount address"), nil, err
|
|
}
|
|
msg := v1.NewMsgDeposit(addr, proposalID, deposit)
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
var fees sdk.Coins
|
|
coins, hasNeg := spendable.SafeSub(deposit...)
|
|
if !hasNeg {
|
|
fees, err = simtypes.RandomFees(r, coins)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, sdk.MsgTypeURL(msg), "unable to generate fees"), 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)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgVote generates a MsgVote with random values.
|
|
func SimulateMsgVote(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
s *SharedState,
|
|
) simtypes.Operation {
|
|
return operationSimulateMsgVote(txGen, ak, bk, k, simtypes.Account{}, -1, s)
|
|
}
|
|
|
|
func operationSimulateMsgVote(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
simAccount simtypes.Account,
|
|
proposalIDInt int64,
|
|
s *SharedState,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context,
|
|
accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
if simAccount.Equals(simtypes.Account{}) {
|
|
simAccount, _ = simtypes.RandomAcc(r, accs)
|
|
}
|
|
|
|
var proposalID uint64
|
|
|
|
switch {
|
|
case proposalIDInt < 0:
|
|
var ok bool
|
|
proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod, s)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgVote, "unable to generate proposalID"), nil, nil
|
|
}
|
|
default:
|
|
proposalID = uint64(proposalIDInt)
|
|
}
|
|
|
|
option := randomVotingOption(r)
|
|
addr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgVote, "unable to get simAccount address"), nil, err
|
|
}
|
|
msg := v1.NewMsgVote(addr, proposalID, option, "")
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
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)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgVoteWeighted generates a MsgVoteWeighted with random values.
|
|
func SimulateMsgVoteWeighted(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
s *SharedState,
|
|
) simtypes.Operation {
|
|
return operationSimulateMsgVoteWeighted(txGen, ak, bk, k, simtypes.Account{}, -1, s)
|
|
}
|
|
|
|
func operationSimulateMsgVoteWeighted(
|
|
txGen client.TxConfig,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
simAccount simtypes.Account,
|
|
proposalIDInt int64,
|
|
s *SharedState,
|
|
) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context,
|
|
accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
if simAccount.Equals(simtypes.Account{}) {
|
|
simAccount, _ = simtypes.RandomAcc(r, accs)
|
|
}
|
|
|
|
var proposalID uint64
|
|
|
|
switch {
|
|
case proposalIDInt < 0:
|
|
var ok bool
|
|
proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod, s)
|
|
if !ok {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgVoteWeighted, "unable to generate proposalID"), nil, nil
|
|
}
|
|
default:
|
|
proposalID = uint64(proposalIDInt)
|
|
}
|
|
|
|
options := randomWeightedVotingOptions(r)
|
|
addr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgVoteWeighted, "unable to get simAccount address"), nil, err
|
|
}
|
|
msg := v1.NewMsgVoteWeighted(addr, proposalID, options, "")
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
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)
|
|
}
|
|
}
|
|
|
|
// SimulateMsgCancelProposal generates a MsgCancelProposal.
|
|
func SimulateMsgCancelProposal(txGen client.TxConfig, ak types.AccountKeeper, bk types.BankKeeper, k *keeper.Keeper) simtypes.Operation {
|
|
return func(
|
|
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context,
|
|
accs []simtypes.Account, chainID string,
|
|
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
|
|
simAccount := accs[0]
|
|
proposal := randomProposal(r, k, ctx)
|
|
if proposal == nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "no proposals found"), nil, nil
|
|
}
|
|
|
|
proposerAddr, err := ak.AddressCodec().BytesToString(simAccount.Address)
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposer"), nil, err
|
|
}
|
|
if proposal.Proposer != proposerAddr {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposer"), nil, nil
|
|
}
|
|
|
|
if (proposal.Status != v1.StatusDepositPeriod) && (proposal.Status != v1.StatusVotingPeriod) {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "invalid proposal status"), nil, nil
|
|
}
|
|
|
|
account := ak.GetAccount(ctx, simAccount.Address)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
accAddr, err := ak.AddressCodec().BytesToString(account.GetAddress())
|
|
if err != nil {
|
|
return simtypes.NoOpMsg(types.ModuleName, TypeMsgCancelProposal, "could not get account address"), nil, err
|
|
}
|
|
msg := v1.NewMsgCancelProposal(proposal.Id, accAddr)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
// Pick a random deposit with a random denomination with a
|
|
// deposit amount between (0, min(balance, minDepositAmount))
|
|
// This is to simulate multiple users depositing to get the
|
|
// proposal above the minimum deposit amount
|
|
func randomDeposit(
|
|
r *rand.Rand,
|
|
ctx sdk.Context,
|
|
ak types.AccountKeeper,
|
|
bk types.BankKeeper,
|
|
k *keeper.Keeper,
|
|
addr sdk.AccAddress,
|
|
useMinAmount bool,
|
|
expedited bool,
|
|
) (deposit sdk.Coins, skip bool, err error) {
|
|
account := ak.GetAccount(ctx, addr)
|
|
spendable := bk.SpendableCoins(ctx, account.GetAddress())
|
|
|
|
if spendable.Empty() {
|
|
return nil, true, nil // skip
|
|
}
|
|
|
|
params, _ := k.Params.Get(ctx)
|
|
minDeposit := params.MinDeposit
|
|
if expedited {
|
|
minDeposit = params.ExpeditedMinDeposit
|
|
}
|
|
denomIndex := r.Intn(len(minDeposit))
|
|
denom := minDeposit[denomIndex].Denom
|
|
|
|
spendableBalance := spendable.AmountOf(denom)
|
|
if spendableBalance.IsZero() {
|
|
return nil, true, nil
|
|
}
|
|
|
|
minDepositAmount := minDeposit[denomIndex].Amount
|
|
|
|
minDepositRatio, err := sdkmath.LegacyNewDecFromStr(params.GetMinDepositRatio())
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
threshold := minDepositAmount.ToLegacyDec().Mul(minDepositRatio).TruncateInt()
|
|
|
|
minAmount := sdkmath.ZeroInt()
|
|
if useMinAmount {
|
|
minDepositPercent, err := sdkmath.LegacyNewDecFromStr(params.MinInitialDepositRatio)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
minAmount = sdkmath.LegacyNewDecFromInt(minDepositAmount).Mul(minDepositPercent).TruncateInt()
|
|
}
|
|
|
|
amount, err := simtypes.RandPositiveInt(r, minDepositAmount.Sub(minAmount))
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
amount = amount.Add(minAmount)
|
|
|
|
if amount.GT(spendableBalance) || amount.LT(threshold) {
|
|
return nil, true, nil
|
|
}
|
|
|
|
return sdk.Coins{sdk.NewCoin(denom, amount)}, false, nil
|
|
}
|
|
|
|
// randomProposal returns a random proposal stored in state
|
|
func randomProposal(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context) *v1.Proposal {
|
|
var proposals []*v1.Proposal
|
|
err := k.Proposals.Walk(ctx, nil, func(key uint64, value v1.Proposal) (stop bool, err error) {
|
|
proposals = append(proposals, &value)
|
|
return false, nil
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
if len(proposals) == 0 {
|
|
return nil
|
|
}
|
|
randomIndex := r.Intn(len(proposals))
|
|
return proposals[randomIndex]
|
|
}
|
|
|
|
// Pick a random proposal ID between the initial proposal ID
|
|
// (defined in gov GenesisState) and the latest proposal ID
|
|
// that matches a given Status.
|
|
// It does not provide a default ID.
|
|
func randomProposalID(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, status v1.ProposalStatus, s *SharedState) (proposalID uint64, found bool) {
|
|
proposalID, _ = k.ProposalID.Peek(ctx)
|
|
if initialProposalID := s.getMinProposalID(); initialProposalID == unsetProposalID {
|
|
s.setMinProposalID(proposalID)
|
|
} else if initialProposalID < proposalID {
|
|
proposalID = uint64(simtypes.RandIntBetween(r, int(initialProposalID), int(proposalID)))
|
|
}
|
|
proposal, err := k.Proposals.Get(ctx, proposalID)
|
|
if err != nil || proposal.Status != status {
|
|
return proposalID, false
|
|
}
|
|
|
|
return proposalID, true
|
|
}
|
|
|
|
// Pick a random voting option
|
|
func randomVotingOption(r *rand.Rand) v1.VoteOption {
|
|
switch r.Intn(4) {
|
|
case 0:
|
|
return v1.OptionYes
|
|
case 1:
|
|
return v1.OptionAbstain
|
|
case 2:
|
|
return v1.OptionNo
|
|
case 3:
|
|
return v1.OptionNoWithVeto
|
|
default:
|
|
panic("invalid vote option")
|
|
}
|
|
}
|
|
|
|
// Pick a random weighted voting options
|
|
func randomWeightedVotingOptions(r *rand.Rand) v1.WeightedVoteOptions {
|
|
w1 := r.Intn(100 + 1)
|
|
w2 := r.Intn(100 - w1 + 1)
|
|
w3 := r.Intn(100 - w1 - w2 + 1)
|
|
w4 := 100 - w1 - w2 - w3
|
|
weightedVoteOptions := v1.WeightedVoteOptions{}
|
|
if w1 > 0 {
|
|
weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
|
|
Option: v1.OptionYes,
|
|
Weight: sdkmath.LegacyNewDecWithPrec(int64(w1), 2).String(),
|
|
})
|
|
}
|
|
if w2 > 0 {
|
|
weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
|
|
Option: v1.OptionAbstain,
|
|
Weight: sdkmath.LegacyNewDecWithPrec(int64(w2), 2).String(),
|
|
})
|
|
}
|
|
if w3 > 0 {
|
|
weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
|
|
Option: v1.OptionNo,
|
|
Weight: sdkmath.LegacyNewDecWithPrec(int64(w3), 2).String(),
|
|
})
|
|
}
|
|
if w4 > 0 {
|
|
weightedVoteOptions = append(weightedVoteOptions, &v1.WeightedVoteOption{
|
|
Option: v1.OptionNoWithVeto,
|
|
Weight: sdkmath.LegacyNewDecWithPrec(int64(w4), 2).String(),
|
|
})
|
|
}
|
|
return weightedVoteOptions
|
|
}
|