cosmos-sdk/x/authz/simulation/operations.go
2024-07-24 12:40:01 +00:00

395 lines
12 KiB
Go

package simulation
import (
"context"
"math/rand"
"time"
gogoprotoany "github.com/cosmos/gogoproto/types/any"
"cosmossdk.io/core/address"
"cosmossdk.io/core/appmodule"
corecontext "cosmossdk.io/core/context"
coregas "cosmossdk.io/core/gas"
coreheader "cosmossdk.io/core/header"
"cosmossdk.io/x/authz"
"cosmossdk.io/x/authz/keeper"
banktype "cosmossdk.io/x/bank/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"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(
registry cdctypes.InterfaceRegistry,
appParams simtypes.AppParams,
cdc codec.JSONCodec,
txGen client.TxConfig,
ak authz.AccountKeeper,
bk authz.BankKeeper,
k keeper.Keeper,
) simulation.WeightedOperations {
var (
weightMsgGrant int
weightExec int
weightRevoke int
)
appParams.GetOrGenerate(OpWeightMsgGrant, &weightMsgGrant, nil, func(_ *rand.Rand) {
weightMsgGrant = WeightGrant
})
appParams.GetOrGenerate(OpWeightExec, &weightExec, nil, func(_ *rand.Rand) {
weightExec = WeightExec
})
appParams.GetOrGenerate(OpWeightRevoke, &weightRevoke, nil, func(_ *rand.Rand) {
weightRevoke = WeightRevoke
})
pCdc := codec.NewProtoCodec(registry)
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgGrant,
SimulateMsgGrant(pCdc, txGen, ak, bk, k),
),
simulation.NewWeightedOperation(
weightExec,
SimulateMsgExec(pCdc, txGen, ak, bk, k, registry),
),
simulation.NewWeightedOperation(
weightRevoke,
SimulateMsgRevoke(pCdc, txGen, ak, bk, k),
),
}
}
// SimulateMsgGrant generates a MsgGrant with random values.
func SimulateMsgGrant(
cdc *codec.ProtoCodec,
txCfg client.TxConfig,
ak authz.AccountKeeper,
bk authz.BankKeeper,
_ 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) {
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, spendableCoins)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err
}
spendLimit := spendableCoins.Sub(fees...)
if len(spendLimit) == 0 {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "spend limit is nil"), nil, nil
}
var expiration *time.Time
t1 := simtypes.RandTimestamp(r)
if !t1.Before(ctx.HeaderInfo().Time) {
expiration = &t1
}
randomAuthz := generateRandomAuthorization(r, spendLimit, ak.AddressCodec())
granterAddr, err := ak.AddressCodec().BytesToString(granter.Address)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "could not get granter address"), nil, nil
}
granteeAddr, err := ak.AddressCodec().BytesToString(grantee.Address)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, "could not get grantee address"), nil, nil
}
msg, err := authz.NewMsgGrant(granterAddr, granteeAddr, randomAuthz, expiration)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err
}
tx, err := simtestutil.GenSignedMockTx(
r,
txCfg,
[]sdk.Msg{msg},
fees,
simtestutil.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, err
}
}
func generateRandomAuthorization(r *rand.Rand, spendLimit sdk.Coins, addressCodec address.Codec) authz.Authorization {
authorizations := make([]authz.Authorization, 2)
sendAuthz := banktype.NewSendAuthorization(spendLimit, nil, addressCodec)
authorizations[0] = sendAuthz
authorizations[1] = authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktype.MsgSend{}))
return authorizations[r.Intn(len(authorizations))]
}
// SimulateMsgRevoke generates a MsgRevoke with random values.
func SimulateMsgRevoke(
cdc *codec.ProtoCodec,
txCfg client.TxConfig,
ak authz.AccountKeeper,
bk authz.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) {
var granterAddr, granteeAddr sdk.AccAddress
var grant authz.Grant
hasGrant := false
err := k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, g authz.Grant) (bool, error) {
grant = g
granterAddr = granter
granteeAddr = grantee
hasGrant = true
return true, nil
})
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, err.Error()), nil, err
}
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.ErrNotFound.Wrapf("account not found")
}
spendableCoins := bk.SpendableCoins(ctx, granterAddr)
fees, err := simtypes.RandomFees(r, 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
}
granterStrAddr, err := ak.AddressCodec().BytesToString(granterAddr)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "could not get granter address"), nil, nil
}
granteeStrAddr, err := ak.AddressCodec().BytesToString(granteeAddr)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "could not get grantee address"), nil, nil
}
msg := authz.NewMsgRevoke(granterStrAddr, granteeStrAddr, a.MsgTypeURL())
account := ak.GetAccount(ctx, granterAddr)
tx, err := simtestutil.GenSignedMockTx(
r,
txCfg,
[]sdk.Msg{&msg},
fees,
simtestutil.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 execute tx: "+err.Error()), nil, err
}
return simtypes.NewOperationMsg(&msg, true, ""), nil, nil
}
}
// SimulateMsgExec generates a MsgExec with random values.
func SimulateMsgExec(
cdc *codec.ProtoCodec,
txCfg client.TxConfig,
ak authz.AccountKeeper,
bk authz.BankKeeper,
k keeper.Keeper,
unpacker gogoprotoany.AnyUnpacker,
) simtypes.Operation {
return func(
r *rand.Rand, app simtypes.AppEntrypoint, ctx sdk.Context, accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
var granterAddr sdk.AccAddress
var granteeAddr sdk.AccAddress
var sendAuth *banktype.SendAuthorization
var err error
err = k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) (bool, error) {
granterAddr = granter
granteeAddr = grantee
var a authz.Authorization
a, err = grant.GetAuthorization()
if err != nil {
return true, err
}
var ok bool
sendAuth, ok = a.(*banktype.SendAuthorization)
return ok, nil
})
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
if sendAuth == nil {
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.ErrNotFound.Wrapf("grantee account not found")
}
if _, ok := simtypes.FindAccount(accs, granterAddr); !ok {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgRevoke, "Account not found"), nil, sdkerrors.ErrNotFound.Wrapf("granter account not found")
}
granterspendableCoins := bk.SpendableCoins(ctx, granterAddr)
coins := simtypes.RandSubsetCoins(r, granterspendableCoins)
// if coins slice is empty, we can not create valid banktype.MsgSend
if len(coins) == 0 {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "empty coins slice"), nil, nil
}
// 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
}
graStr, err := ak.AddressCodec().BytesToString(granterAddr)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
greStr, err := ak.AddressCodec().BytesToString(granteeAddr)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
msg := []sdk.Msg{banktype.NewMsgSend(graStr, greStr, coins)}
goCtx := context.WithValue(ctx.Context(), corecontext.EnvironmentContextKey, appmodule.Environment{
HeaderService: headerService{},
GasService: mockGasService{},
})
_, err = sendAuth.Accept(goCtx, msg[0])
if err != nil {
if sdkerrors.ErrInsufficientFunds.Is(err) {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, nil
}
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, err.Error()), nil, err
}
msgExec := authz.NewMsgExec(greStr, msg)
granteeSpendableCoins := bk.SpendableCoins(ctx, granteeAddr)
fees, err := simtypes.RandomFees(r, granteeSpendableCoins)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "fee error"), nil, err
}
granteeAcc := ak.GetAccount(ctx, granteeAddr)
tx, err := simtestutil.GenSignedMockTx(
r,
txCfg,
[]sdk.Msg{&msgExec},
fees,
simtestutil.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(unpacker)
if err != nil {
return simtypes.NoOpMsg(authz.ModuleName, TypeMsgExec, "unmarshal error"), nil, err
}
return simtypes.NewOperationMsg(&msgExec, true, "success"), nil, nil
}
}
type headerService struct{}
func (h headerService) HeaderInfo(ctx context.Context) coreheader.Info {
return sdk.UnwrapSDKContext(ctx).HeaderInfo()
}
type mockGasService struct {
coregas.Service
}
func (m mockGasService) GasMeter(ctx context.Context) coregas.Meter {
return mockGasMeter{}
}
type mockGasMeter struct {
coregas.Meter
}
func (m mockGasMeter) Consume(amount coregas.Gas, descriptor string) error {
return nil
}