0g-chain/x/issuance/simulation/operations.go
Kevin Davis fca16da84a
fix: return if supply limit hit in issuance sims (#635)
* fix: return if supply limit hit in issuance sims
2020-08-21 19:39:20 -04:00

323 lines
10 KiB
Go

package simulation
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
simappparams "github.com/kava-labs/kava/app/params"
"github.com/kava-labs/kava/x/issuance/keeper"
"github.com/kava-labs/kava/x/issuance/types"
)
// Simulation operation weights constants
const (
OpWeightMsgIssue = "op_weight_msg_issue"
OpWeightMsgRedeem = "op_weight_msg_redeem"
OpWeightMsgBlock = "op_weight_msg_block"
OpWeightMsgPause = "op_weight_msg_pause"
)
// WeightedOperations returns all the operations from the module with their respective weights
func WeightedOperations(
appParams simulation.AppParams, cdc *codec.Codec, ak types.AccountKeeper, k keeper.Keeper,
) simulation.WeightedOperations {
var (
weightMsgIssue int
weightMsgReedem int
weightMsgBlock int
weightMsgPause int
)
appParams.GetOrGenerate(cdc, OpWeightMsgIssue, &weightMsgIssue, nil,
func(_ *rand.Rand) {
weightMsgIssue = simappparams.DefaultWeightMsgIssue
},
)
appParams.GetOrGenerate(cdc, OpWeightMsgRedeem, &weightMsgReedem, nil,
func(_ *rand.Rand) {
weightMsgReedem = simappparams.DefaultWeightMsgRedeem
},
)
appParams.GetOrGenerate(cdc, OpWeightMsgBlock, &weightMsgBlock, nil,
func(_ *rand.Rand) {
weightMsgBlock = simappparams.DefaultWeightMsgBlock
},
)
appParams.GetOrGenerate(cdc, OpWeightMsgPause, &weightMsgPause, nil,
func(_ *rand.Rand) {
weightMsgPause = simappparams.DefaultWeightMsgPause
},
)
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgIssue,
SimulateMsgIssueTokens(ak, k),
),
simulation.NewWeightedOperation(
weightMsgReedem,
SimulateMsgRedeemTokens(ak, k),
),
simulation.NewWeightedOperation(
weightMsgBlock,
SimulateMsgBlockAddress(ak, k),
),
simulation.NewWeightedOperation(
weightMsgPause,
SimulateMsgPause(ak, k),
),
}
}
// SimulateMsgIssueTokens generates a MsgIssueTokens with random values
func SimulateMsgIssueTokens(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// shuffle the assets and get a random one
assets := k.GetParams(ctx).Assets
r.Shuffle(len(assets), func(i, j int) {
assets[i], assets[j] = assets[j], assets[i]
})
asset := assets[0]
if asset.Paused {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
// make sure owner account exists
ownerSimAcc, found := simulation.FindAccount(accs, asset.Owner)
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("asset owner not found: %s", asset)
}
// issue new tokens to the owner 50% of the time so we have funds to redeem
ownerAcc := ak.GetAccount(ctx, asset.Owner)
recipient := ownerAcc
if r.Intn(2) == 0 {
simAccount, _ := simulation.RandomAcc(r, accs)
recipient = ak.GetAccount(ctx, simAccount.Address)
}
if recipient == nil {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
for _, blockedAddr := range asset.BlockedAddresses {
if recipient.GetAddress().Equals(blockedAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
}
randomAmount := simulation.RandIntBetween(r, 10000000, 1000000000000)
if asset.RateLimit.Active {
supply, found := k.GetAssetSupply(ctx, asset.Denom)
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("issuance - no asset supply for %s", asset.Denom)
}
if asset.RateLimit.Limit.LT(supply.CurrentSupply.Amount.Add(sdk.NewInt(int64(randomAmount)))) {
maxAmount := asset.RateLimit.Limit.Sub(supply.CurrentSupply.Amount)
if maxAmount.IsPositive() && maxAmount.GT(sdk.OneInt()) {
randomAmount = simulation.RandIntBetween(r, 1, int(maxAmount.Int64()))
} else if maxAmount.Equal(sdk.OneInt()) {
randomAmount = 1
} else {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
}
}
msg := types.NewMsgIssueTokens(asset.Owner, sdk.NewCoin(asset.Denom, sdk.NewInt(int64(randomAmount))), recipient.GetAddress())
spendableCoins := ownerAcc.SpendableCoins(ctx.BlockTime())
fees, err := simulation.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{ownerAcc.GetAccountNumber()},
[]uint64{ownerAcc.GetSequence()},
ownerSimAcc.PrivKey,
)
_, _, err = app.Deliver(tx)
if err != nil {
fmt.Printf("error on issue %s\n%s\n", msg, asset)
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgRedeemTokens generates a MsgRedeemTokens with random values
func SimulateMsgRedeemTokens(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// shuffle the assets and get a random one
assets := k.GetParams(ctx).Assets
r.Shuffle(len(assets), func(i, j int) {
assets[i], assets[j] = assets[j], assets[i]
})
asset := assets[0]
if asset.Paused {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
// make sure owner account exists
ownerSimAcc, found := simulation.FindAccount(accs, asset.Owner)
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("asset owner not found: %s", asset)
}
ownerAcc := ak.GetAccount(ctx, asset.Owner)
spendableCoinAmount := ownerAcc.SpendableCoins(ctx.BlockTime()).AmountOf(asset.Denom)
if spendableCoinAmount.IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
var redeemAmount sdk.Int
if spendableCoinAmount.Equal(sdk.OneInt()) {
redeemAmount = sdk.OneInt()
} else {
redeemAmount = sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(spendableCoinAmount.Int64()))))
}
msg := types.NewMsgRedeemTokens(asset.Owner, sdk.NewCoin(asset.Denom, redeemAmount))
spendableCoins := ownerAcc.SpendableCoins(ctx.BlockTime()).Sub(sdk.NewCoins(sdk.NewCoin(asset.Denom, redeemAmount)))
fees, err := simulation.RandomFees(r, ctx, spendableCoins)
if err != nil {
fmt.Printf("%s\n", msg)
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{ownerAcc.GetAccountNumber()},
[]uint64{ownerAcc.GetSequence()},
ownerSimAcc.PrivKey,
)
_, _, err = app.Deliver(tx)
if err != nil {
fmt.Printf("error on redeem %s\n%s\n", msg, asset)
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgBlockAddress generates a MsgBlockAddress with random values
func SimulateMsgBlockAddress(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// shuffle the assets and get a random one
assets := k.GetParams(ctx).Assets
r.Shuffle(len(assets), func(i, j int) {
assets[i], assets[j] = assets[j], assets[i]
})
asset := assets[0]
// make sure owner account exists
ownerSimAcc, found := simulation.FindAccount(accs, asset.Owner)
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("asset owner not found: %s", asset)
}
ownerAcc := ak.GetAccount(ctx, asset.Owner)
// find an account to block
simAccount, _ := simulation.RandomAcc(r, accs)
blockedAccount := ak.GetAccount(ctx, simAccount.Address)
if blockedAccount.GetAddress().Equals(asset.Owner) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
for _, blockedAddr := range asset.BlockedAddresses {
if blockedAccount.GetAddress().Equals(blockedAddr) {
return simulation.NoOpMsg(types.ModuleName), nil, nil
}
}
msg := types.NewMsgBlockAddress(asset.Owner, asset.Denom, blockedAccount.GetAddress())
spendableCoins := ownerAcc.SpendableCoins(ctx.BlockTime())
fees, err := simulation.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas*2,
chainID,
[]uint64{ownerAcc.GetAccountNumber()},
[]uint64{ownerAcc.GetSequence()},
ownerSimAcc.PrivKey,
)
_, _, err = app.Deliver(tx)
if err != nil {
fmt.Printf("error on block %s\n%s\n", msg, asset)
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}
// SimulateMsgPause generates a MsgChangePauseStatus with random values
func SimulateMsgPause(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
// shuffle the assets and get a random one
assets := k.GetParams(ctx).Assets
r.Shuffle(len(assets), func(i, j int) {
assets[i], assets[j] = assets[j], assets[i]
})
asset := assets[0]
// make sure owner account exists
ownerSimAcc, found := simulation.FindAccount(accs, asset.Owner)
if !found {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("asset owner not found: %s", asset)
}
ownerAcc := ak.GetAccount(ctx, asset.Owner)
// set status to paused/un-paused
status := r.Intn(2) == 0
msg := types.NewMsgSetPauseStatus(asset.Owner, asset.Denom, status)
spendableCoins := ownerAcc.SpendableCoins(ctx.BlockTime())
fees, err := simulation.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas*2,
chainID,
[]uint64{ownerAcc.GetAccountNumber()},
[]uint64{ownerAcc.GetSequence()},
ownerSimAcc.PrivKey,
)
_, _, err = app.Deliver(tx)
if err != nil {
fmt.Printf("error on pause %s\n%s\n", msg, asset)
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, ""), nil, nil
}
}