-
Notifications
You must be signed in to change notification settings - Fork 697
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add GovVoteDecorator * add changelog entries * fix linter * add unit test * fix linter * Update ante/gov_vote_ante.go Co-authored-by: MSalopek <matija.salopek994@gmail.com> --------- Co-authored-by: MSalopek <matija.salopek994@gmail.com>
- Loading branch information
Showing
6 changed files
with
247 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
- Add ante handler that only allows `MsgVote` messages from accounts with at least | ||
1 atom staked. ([\#2912](https://github.com/cosmos/gaia/pull/2912)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
- Add ante handler that only allows `MsgVote` messages from accounts with at least | ||
1 atom staked. ([\#2912](https://github.com/cosmos/gaia/pull/2912)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package ante | ||
|
||
import ( | ||
errorsmod "cosmossdk.io/errors" | ||
|
||
"github.com/cosmos/cosmos-sdk/codec" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
"github.com/cosmos/cosmos-sdk/x/authz" | ||
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" | ||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
|
||
gaiaerrors "github.com/cosmos/gaia/v15/types/errors" | ||
) | ||
|
||
var ( | ||
minStakedTokens = sdk.NewDec(1000000) // 1_000_000 uatom (or 1 atom) | ||
maxDelegationsChecked = 100 // number of delegation to check for the minStakedTokens | ||
) | ||
|
||
type GovVoteDecorator struct { | ||
stakingKeeper *stakingkeeper.Keeper | ||
cdc codec.BinaryCodec | ||
} | ||
|
||
func NewGovVoteDecorator(cdc codec.BinaryCodec, stakingKeeper *stakingkeeper.Keeper) GovVoteDecorator { | ||
return GovVoteDecorator{ | ||
stakingKeeper: stakingKeeper, | ||
cdc: cdc, | ||
} | ||
} | ||
|
||
func (g GovVoteDecorator) AnteHandle( | ||
ctx sdk.Context, tx sdk.Tx, | ||
simulate bool, next sdk.AnteHandler, | ||
) (newCtx sdk.Context, err error) { | ||
// do not run check during simulations | ||
if simulate { | ||
return next(ctx, tx, simulate) | ||
} | ||
|
||
msgs := tx.GetMsgs() | ||
if err = g.ValidateVoteMsgs(ctx, msgs); err != nil { | ||
return ctx, err | ||
} | ||
|
||
return next(ctx, tx, simulate) | ||
} | ||
|
||
// ValidateVoteMsgs checks if a voter has enough stake to vote | ||
func (g GovVoteDecorator) ValidateVoteMsgs(ctx sdk.Context, msgs []sdk.Msg) error { | ||
validMsg := func(m sdk.Msg) error { | ||
if msg, ok := m.(*govv1beta1.MsgVote); ok { | ||
accAddr, err := sdk.AccAddressFromBech32(msg.Voter) | ||
if err != nil { | ||
return err | ||
} | ||
enoughStake := false | ||
delegationCount := 0 | ||
stakedTokens := sdk.NewDec(0) | ||
g.stakingKeeper.IterateDelegatorDelegations(ctx, accAddr, func(delegation stakingtypes.Delegation) bool { | ||
validatorAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress) | ||
if err != nil { | ||
panic(err) // shouldn't happen | ||
} | ||
validator, found := g.stakingKeeper.GetValidator(ctx, validatorAddr) | ||
if found { | ||
shares := delegation.Shares | ||
tokens := validator.TokensFromSharesTruncated(shares) | ||
stakedTokens = stakedTokens.Add(tokens) | ||
if stakedTokens.GTE(minStakedTokens) { | ||
enoughStake = true | ||
return true // break the iteration | ||
} | ||
} | ||
delegationCount++ | ||
// break the iteration if maxDelegationsChecked were already checked | ||
return delegationCount >= maxDelegationsChecked | ||
}) | ||
if !enoughStake { | ||
return errorsmod.Wrapf(gaiaerrors.ErrInsufficientStake, "insufficient stake for voting - min required %v", minStakedTokens) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
validAuthz := func(execMsg *authz.MsgExec) error { | ||
for _, v := range execMsg.Msgs { | ||
var innerMsg sdk.Msg | ||
if err := g.cdc.UnpackAny(v, &innerMsg); err != nil { | ||
return errorsmod.Wrap(gaiaerrors.ErrUnauthorized, "cannot unmarshal authz exec msgs") | ||
} | ||
if err := validMsg(innerMsg); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
for _, m := range msgs { | ||
if msg, ok := m.(*authz.MsgExec); ok { | ||
if err := validAuthz(msg); err != nil { | ||
return err | ||
} | ||
continue | ||
} | ||
|
||
// validate normal msgs | ||
if err := validMsg(m); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package ante_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
tmproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
|
||
"cosmossdk.io/math" | ||
|
||
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
govv1beta1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
|
||
"github.com/cosmos/gaia/v15/ante" | ||
"github.com/cosmos/gaia/v15/app/helpers" | ||
) | ||
|
||
func TestVoteSpamDecorator(t *testing.T) { | ||
gaiaApp := helpers.Setup(t) | ||
ctx := gaiaApp.NewUncachedContext(true, tmproto.Header{}) | ||
decorator := ante.NewGovVoteDecorator(gaiaApp.AppCodec(), gaiaApp.StakingKeeper) | ||
stakingKeeper := gaiaApp.StakingKeeper | ||
|
||
// Get validator | ||
valAddr1 := stakingKeeper.GetAllValidators(ctx)[0].GetOperator() | ||
|
||
// Create one more validator | ||
pk := ed25519.GenPrivKeyFromSecret([]byte{uint8(13)}).PubKey() | ||
validator2, err := stakingtypes.NewValidator( | ||
sdk.ValAddress(pk.Address()), | ||
pk, | ||
stakingtypes.Description{}, | ||
) | ||
valAddr2 := validator2.GetOperator() | ||
require.NoError(t, err) | ||
// Make sure the validator is bonded so it's not removed on Undelegate | ||
validator2.Status = stakingtypes.Bonded | ||
stakingKeeper.SetValidator(ctx, validator2) | ||
err = stakingKeeper.SetValidatorByConsAddr(ctx, validator2) | ||
require.NoError(t, err) | ||
stakingKeeper.SetNewValidatorByPowerIndex(ctx, validator2) | ||
err = stakingKeeper.Hooks().AfterValidatorCreated(ctx, validator2.GetOperator()) | ||
require.NoError(t, err) | ||
|
||
// Get delegator (this account was created during setup) | ||
addr := gaiaApp.AccountKeeper.GetAccountAddressByID(ctx, 0) | ||
delegator, err := sdk.AccAddressFromBech32(addr) | ||
require.NoError(t, err) | ||
|
||
tests := []struct { | ||
name string | ||
bondAmt math.Int | ||
validators []sdk.ValAddress | ||
expectPass bool | ||
}{ | ||
{ | ||
name: "delegate 0.1 atom", | ||
bondAmt: sdk.NewInt(100000), | ||
validators: []sdk.ValAddress{valAddr1}, | ||
expectPass: false, | ||
}, | ||
{ | ||
name: "delegate 1 atom", | ||
bondAmt: sdk.NewInt(1000000), | ||
validators: []sdk.ValAddress{valAddr1}, | ||
expectPass: true, | ||
}, | ||
{ | ||
name: "delegate 1 atom to two validators", | ||
bondAmt: sdk.NewInt(1000000), | ||
validators: []sdk.ValAddress{valAddr1, valAddr2}, | ||
expectPass: true, | ||
}, | ||
{ | ||
name: "delegate 0.9 atom to two validators", | ||
bondAmt: sdk.NewInt(900000), | ||
validators: []sdk.ValAddress{valAddr1, valAddr2}, | ||
expectPass: false, | ||
}, | ||
{ | ||
name: "delegate 10 atom", | ||
bondAmt: sdk.NewInt(10000000), | ||
validators: []sdk.ValAddress{valAddr1}, | ||
expectPass: true, | ||
}, | ||
} | ||
|
||
for _, tc := range tests { | ||
// Unbond all tokens for this delegator | ||
delegations := stakingKeeper.GetAllDelegatorDelegations(ctx, delegator) | ||
for _, del := range delegations { | ||
_, err := stakingKeeper.Undelegate(ctx, delegator, del.GetValidatorAddr(), del.GetShares()) | ||
require.NoError(t, err) | ||
} | ||
|
||
// Delegate tokens | ||
amt := tc.bondAmt.Quo(sdk.NewInt(int64(len(tc.validators)))) | ||
for _, valAddr := range tc.validators { | ||
val, found := stakingKeeper.GetValidator(ctx, valAddr) | ||
require.True(t, found) | ||
_, err := stakingKeeper.Delegate(ctx, delegator, amt, stakingtypes.Unbonded, val, true) | ||
require.NoError(t, err) | ||
} | ||
|
||
// Create vote message | ||
msg := govv1beta1.NewMsgVote( | ||
delegator, | ||
0, | ||
govv1beta1.OptionYes, | ||
) | ||
|
||
// Validate vote message | ||
err := decorator.ValidateVoteMsgs(ctx, []sdk.Msg{msg}) | ||
if tc.expectPass { | ||
require.NoError(t, err, "expected %v to pass", tc.name) | ||
} else { | ||
require.Error(t, err, "expected %v to fail", tc.name) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters