Skip to content

Commit

Permalink
fix: intercept staking module messages inside MsgExec (#138)
Browse files Browse the repository at this point in the history
Resolves #127

This PR fixes the `DropValidatorMsgDecorator` AnteHandler to handle
disallowed staking related messages inside `MsgExec` (ref
https://jumpcrypto.com/writing/bypassing-ethermint-ante-handlers/).
  • Loading branch information
SebastianElvis authored Oct 7, 2024
1 parent 89dd901 commit b128f1d
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 22 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
transaction fee refunding mechanism for covenant signatures and finality signatures
* [#125](https://github.com/babylonlabs-io/babylon/pull/125) Implement ADR-028 and
refund transaction fee for certain transactions from protocol stakeholders
* [#138](https://github.com/babylonlabs-io/babylon/pull/138) Intercept staking module
messages inside `authz.MsgExec`

### Misc Improvements

Expand Down
37 changes: 29 additions & 8 deletions x/epoching/keeper/drop_validator_msg_decorator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package keeper
import (
epochingtypes "github.com/babylonlabs-io/babylon/x/epoching/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authz "github.com/cosmos/cosmos-sdk/x/authz"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

Expand Down Expand Up @@ -32,20 +33,40 @@ func (qmd DropValidatorMsgDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
}
// after genesis, if validator-related message, reject msg
for _, msg := range tx.GetMsgs() {
if qmd.IsValidatorRelatedMsg(msg) {
return ctx, epochingtypes.ErrUnwrappedMsgType
}
err := qmd.ValidateMsg(msg)
return ctx, err
}

return next(ctx, tx, simulate)
}

// IsValidatorRelatedMsg checks if the given message is of non-wrapped type, which should be rejected
func (qmd DropValidatorMsgDecorator) IsValidatorRelatedMsg(msg sdk.Msg) bool {
switch msg.(type) {
// ValidateMsg checks if the given message is of non-wrapped type, which should be rejected
// It returns true if the message is a validator-related message, and false otherwise.
// It returns an error if it is a MsgExec message which contains invalid messages.
func (qmd DropValidatorMsgDecorator) ValidateMsg(msg sdk.Msg) error {
switch msg := msg.(type) {
case *stakingtypes.MsgCreateValidator, *stakingtypes.MsgDelegate, *stakingtypes.MsgUndelegate, *stakingtypes.MsgBeginRedelegate, *stakingtypes.MsgCancelUnbondingDelegation:
return true
// validator-related message
return epochingtypes.ErrUnwrappedMsgType
case *authz.MsgExec:
// MsgExec might contain a validator-related message and those should
// not bypass the ante handler
// https://jumpcrypto.com/writing/bypassing-ethermint-ante-handlers/
// unpack the exec message
internalMsgs, err := msg.GetMessages()
if err != nil {
// the internal message is not valid
return err
}
// check if any of the internal messages is a validator-related message
for _, internalMsg := range internalMsgs {
// recursively validate the internal message
if err := qmd.ValidateMsg(internalMsg); err != nil {
return err
}
}
return nil
default:
return false
return nil
}
}
48 changes: 34 additions & 14 deletions x/epoching/keeper/drop_validator_msg_decorator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,55 @@ package keeper
import (
"testing"

"github.com/babylonlabs-io/babylon/x/epoching/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authz "github.com/cosmos/cosmos-sdk/x/authz"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/require"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func newMsgExecWithStakingMsg() *authz.MsgExec {
msg := authz.NewMsgExec(sdk.AccAddress("test"), []sdk.Msg{
&stakingtypes.MsgCreateValidator{},
&stakingtypes.MsgEditValidator{},
})
return &msg
}

func newValidMsgExec() *authz.MsgExec {
msg := authz.NewMsgExec(sdk.AccAddress("test"), []sdk.Msg{
&stakingtypes.MsgEditValidator{},
})
return &msg
}

func TestDropValidatorMsgDecorator(t *testing.T) {
testCases := []struct {
msg sdk.Msg
expectPass bool
msg sdk.Msg
expectErr error
}{
// wrapped message types that should be rejected
{&stakingtypes.MsgCreateValidator{}, true},
{&stakingtypes.MsgDelegate{}, true},
{&stakingtypes.MsgUndelegate{}, true},
{&stakingtypes.MsgBeginRedelegate{}, true},
{&stakingtypes.MsgCancelUnbondingDelegation{}, true},
{&stakingtypes.MsgCreateValidator{}, types.ErrUnwrappedMsgType},
{&stakingtypes.MsgDelegate{}, types.ErrUnwrappedMsgType},
{&stakingtypes.MsgUndelegate{}, types.ErrUnwrappedMsgType},
{&stakingtypes.MsgBeginRedelegate{}, types.ErrUnwrappedMsgType},
{&stakingtypes.MsgCancelUnbondingDelegation{}, types.ErrUnwrappedMsgType},
// MsgExec that contains staking messages should be rejected
{newMsgExecWithStakingMsg(), types.ErrUnwrappedMsgType},
// allowed message types
{&stakingtypes.MsgEditValidator{}, false},
{&stakingtypes.MsgEditValidator{}, nil},
{newValidMsgExec(), nil},
}

decorator := NewDropValidatorMsgDecorator(&Keeper{})

for _, tc := range testCases {
res := decorator.IsValidatorRelatedMsg(tc.msg)
if tc.expectPass {
require.True(t, res)
err := decorator.ValidateMsg(tc.msg)
if tc.expectErr != nil {
require.Error(t, err)
require.Equal(t, tc.expectErr, err)
} else {
require.False(t, res)
require.NoError(t, err)
}
}
}

0 comments on commit b128f1d

Please sign in to comment.