cosmos-sdk/x/authz/simulation/operations.go
MD Aleem a426780e71
fix: x/authz allow insufficient funds error (#11252)
## Description

Allow insufficient funds error for authz simulation


Closes: #XXXX



---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2022-03-01 10:06:20 +00:00

306 lines
9.7 KiB
Go

package simulation
import (
"math/rand"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/authz"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/authz/keeper"
banktype "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// authz message types
var (
TypeMsgGrant = sdk.MsgTypeURL(&authz.MsgGrant{})
TypeMsgRevoke = sdk.MsgTypeURL(&authz.MsgRevoke{})
TypeMsgExec = sdk.MsgTypeURL(&authz.MsgExec{})
)
// Simulation operation weights constants
const (
OpWeightMsgGrant = "op_weight_msg_grant"
OpWeightRevoke = "op_weight_msg_revoke"
OpWeightExec = "op_weight_msg_execute"
)
// authz operations weights
const (
WeightGrant = 100
WeightRevoke = 90
WeightExec = 90
)
// WeightedOperations returns all the operations from the module with their respective weights
func WeightedOperations(
appParams simtypes.AppParams, cdc codec.JSONCodec, ak authz.AccountKeeper, bk authz.BankKeeper, k keeper.Keeper, appCdc cdctypes.AnyUnpacker) simulation.WeightedOperations {
var (
weightMsgGrant int
weightExec int
weightRevoke int
)
appParams.GetOrGenerate(cdc, OpWeightMsgGrant, &weightMsgGrant, nil,
func(_ *rand.Rand) {
weightMsgGrant = WeightGrant
},
)
appParams.GetOrGenerate(cdc, OpWeightExec, &weightExec, nil,
func(_ *rand.Rand) {
weightExec = WeightExec
},
)
appParams.GetOrGenerate(cdc, OpWeightRevoke, &weightRevoke, nil,
func(_ *rand.Rand) {
weightRevoke = WeightRevoke
},
)
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgGrant,
SimulateMsgGrant(ak, bk, k),
),
simulation.NewWeightedOperation(
weightExec,
SimulateMsgExec(ak, bk, k, appCdc),
),
simulation.NewWeightedOperation(
weightRevoke,
SimulateMsgRevoke(ak, bk, k),
),
}
}
// SimulateMsgGrant generates a MsgGrant with random values.
func SimulateMsgGrant(ak authz.AccountKeeper, bk authz.BankKeeper, _ keeper.Keeper) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
granter, _ := simtypes.RandomAcc(r, accs)
grantee, _ := simtypes.RandomAcc(r, accs)
if granter.Address.Equals(grantee.Address) {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "granter and grantee are same"), nil, nil
}
granterAcc := ak.GetAccount(ctx, granter.Address)
spendableCoins := bk.SpendableCoins(ctx, granter.Address)
fees, err := simtypes.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err
}
spendLimit := spendableCoins.Sub(fees)
if spendLimit == nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "spend limit is nil"), nil, nil
}
expiration := simtypes.RandTimestamp(r)
if expiration.Before(ctx.BlockTime()) {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "past time"), nil, nil
}
msg, err := authz.NewMsgGrant(granter.Address, grantee.Address, generateRandomAuthorization(r, spendLimit), expiration)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err
}
txCfg := simappparams.MakeTestEncodingConfig().TxConfig
tx, err := helpers.GenTx(
txCfg,
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{granterAcc.GetAccountNumber()},
[]uint64{granterAcc.GetSequence()},
granter.PrivKey,
)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "unable to generate mock tx"), nil, err
}
_, _, err = app.SimDeliver(txCfg.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, sdk.MsgTypeURL(msg), "unable to deliver tx"), nil, err
}
return simtypes.NewOperationMsg(msg, true, "", nil), nil, err
}
}
func generateRandomAuthorization(r *rand.Rand, spendLimit sdk.Coins) authz.Authorization {
authorizations := make([]authz.Authorization, 2)
authorizations[0] = banktype.NewSendAuthorization(spendLimit)
authorizations[1] = authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktype.MsgSend{}))
return authorizations[r.Intn(len(authorizations))]
}
// SimulateMsgRevoke generates a MsgRevoke with random values.
func SimulateMsgRevoke(ak authz.AccountKeeper, bk authz.BankKeeper, k keeper.Keeper) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
var granterAddr, granteeAddr sdk.AccAddress
var grant authz.Grant
hasGrant := false
k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, g authz.Grant) bool {
grant = g
granterAddr = granter
granteeAddr = grantee
hasGrant = true
return true
})
if !hasGrant {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "no grants"), nil, nil
}
granterAcc, ok := simtypes.FindAccount(accs, granterAddr)
if !ok {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "account not found"), nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "account not found")
}
spendableCoins := bk.SpendableCoins(ctx, granterAddr)
fees, err := simtypes.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "fee error"), nil, err
}
a, err := grant.GetAuthorization()
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "authorization error"), nil, err
}
msg := authz.NewMsgRevoke(granterAddr, granteeAddr, a.MsgTypeURL())
txCfg := simappparams.MakeTestEncodingConfig().TxConfig
account := ak.GetAccount(ctx, granterAddr)
tx, err := helpers.GenTx(
txCfg,
[]sdk.Msg{&msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
granterAcc.PrivKey,
)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, err.Error()), nil, err
}
_, _, err = app.SimDeliver(txCfg.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "unable to deliver tx"), nil, err
}
return simtypes.NewOperationMsg(&msg, true, "", nil), nil, nil
}
}
// SimulateMsgExec generates a MsgExec with random values.
func SimulateMsgExec(ak authz.AccountKeeper, bk authz.BankKeeper, k keeper.Keeper, cdc cdctypes.AnyUnpacker) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
hasGrant := false
var targetGrant authz.Grant
var granterAddr sdk.AccAddress
var granteeAddr sdk.AccAddress
k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) bool {
targetGrant = grant
granterAddr = granter
granteeAddr = grantee
hasGrant = true
return true
})
if !hasGrant {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "no grant found"), nil, nil
}
grantee, ok := simtypes.FindAccount(accs, granteeAddr)
if !ok {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "Account not found"), nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "grantee account not found")
}
if _, ok := simtypes.FindAccount(accs, granterAddr); !ok {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "Account not found"), nil, sdkerrors.Wrapf(sdkerrors.ErrNotFound, "granter account not found")
}
granterspendableCoins := bk.SpendableCoins(ctx, granterAddr)
coins := simtypes.RandSubsetCoins(r, granterspendableCoins)
// Check send_enabled status of each sent coin denom
if err := bk.IsSendEnabledCoins(ctx, coins...); err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil
}
msg := []sdk.Msg{banktype.NewMsgSend(granterAddr, granteeAddr, coins)}
authorization, err := targetGrant.GetAuthorization()
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
sendAuth, ok := authorization.(*banktype.SendAuthorization)
if !ok {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "not a send authorization"), nil, nil
}
_, err = sendAuth.Accept(ctx, msg[0])
if err != nil {
if sdkerrors.ErrInsufficientFunds.Is(err) {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil
} else {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
}
msgExec := authz.NewMsgExec(granteeAddr, msg)
granteeSpendableCoins := bk.SpendableCoins(ctx, granteeAddr)
fees, err := simtypes.RandomFees(r, ctx, granteeSpendableCoins)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "fee error"), nil, err
}
txCfg := simappparams.MakeTestEncodingConfig().TxConfig
granteeAcc := ak.GetAccount(ctx, granteeAddr)
tx, err := helpers.GenTx(
txCfg,
[]sdk.Msg{&msgExec},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{granteeAcc.GetAccountNumber()},
[]uint64{granteeAcc.GetSequence()},
grantee.PrivKey,
)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
_, _, err = app.SimDeliver(txCfg.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
err = msgExec.UnpackInterfaces(cdc)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "unmarshal error"), nil, err
}
return simtypes.NewOperationMsg(&msgExec, true, "success", nil), nil, nil
}
}