395 lines
12 KiB
Go
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
|
|
}
|