Skip to content

Commit

Permalink
feat(ante)!: Ante handler to add a maximum commission rate of 25% for…
Browse files Browse the repository at this point in the history
… validators. (#1615)

* feat(ante)!: Ante handler to add a maximum commission rate of 25% for validators.

* test: fix broken tests

---------

Co-authored-by: Matthias <[email protected]>
  • Loading branch information
Unique-Divine and matthiasmatt authored Oct 2, 2023
1 parent c3de785 commit 6c24125
Show file tree
Hide file tree
Showing 11 changed files with 226 additions and 25 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [#1609](https://github.com/NibiruChain/nibiru/pull/1609) - refactor(app)!: Remove x/stablecoin module.
* [#1613](https://github.com/NibiruChain/nibiru/pull/1613) - feat(app)!: enforce min commission by changing default and genesis validation
* [#1615](https://github.com/NibiruChain/nibiru/pull/1613) - feat(ante)!: Ante
handler to add a maximum commission rate of 25% for validators.

### Improvements

Expand Down Expand Up @@ -666,4 +668,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Testing

* [#695](https://github.com/NibiruChain/nibiru/pull/695) Add `OpenPosition` integration tests.
* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods.
* [#692](https://github.com/NibiruChain/nibiru/pull/692) Add test coverage for Perp MsgServer methods.
1 change: 1 addition & 0 deletions app/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func NewAnteHandler(options AnteHandlerOptions) (sdk.AnteHandler, error) {
sdkante.NewTxTimeoutHeightDecorator(),
sdkante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewPostPriceFixedPriceDecorator(),
ante.AnteDecoratorStakingCommission{},
sdkante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
// Replace fee ante from cosmos auth with a custom one.
sdkante.NewDeductFeeDecorator(
Expand Down
37 changes: 37 additions & 0 deletions app/ante/commission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

func MAX_COMMISSION() sdk.Dec { return sdk.MustNewDecFromStr("0.25") }

var _ sdk.AnteDecorator = (*AnteDecoratorStakingCommission)(nil)

// AnteDecoratorStakingCommission: Implements sdk.AnteDecorator, enforcing the
// maximum staking commission for validators on the network.
type AnteDecoratorStakingCommission struct{}

func (a AnteDecoratorStakingCommission) AnteHandle(
ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler,
) (newCtx sdk.Context, err error) {
for _, msg := range tx.GetMsgs() {
switch msg := msg.(type) {
case *stakingtypes.MsgCreateValidator:
rate := msg.Commission.Rate
if rate.GT(MAX_COMMISSION()) {
return ctx, NewErrMaxValidatorCommission(rate)
}
case *stakingtypes.MsgEditValidator:
rate := msg.CommissionRate
if rate != nil && msg.CommissionRate.GT(MAX_COMMISSION()) {
return ctx, NewErrMaxValidatorCommission(*rate)
}
default:
continue
}
}

return next(ctx, tx, simulate)
}
138 changes: 138 additions & 0 deletions app/ante/commission_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package ante_test

import (
"testing"

sdkclienttx "github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"

"github.com/NibiruChain/nibiru/app"
"github.com/NibiruChain/nibiru/app/ante"
"github.com/NibiruChain/nibiru/x/common/testutil"
)

func (s *AnteTestSuite) TestAnteDecoratorStakingCommission() {
// nextAnteHandler: A no-op next handler to make this a unit test.
var nextAnteHandler sdk.AnteHandler = func(
ctx sdk.Context, tx sdk.Tx, simulate bool,
) (newCtx sdk.Context, err error) {
return ctx, nil
}

mockDescription := stakingtypes.Description{
Moniker: "mock-moniker",
Identity: "mock-identity",
Website: "mock-website",
SecurityContact: "mock-security-contact",
Details: "mock-details",
}

valAddr := sdk.ValAddress(testutil.AccAddress()).String()
commissionRatePointer := new(sdk.Dec)
*commissionRatePointer = sdk.NewDecWithPrec(10, 2)
happyMsgs := []sdk.Msg{
&stakingtypes.MsgCreateValidator{
Description: mockDescription,
Commission: stakingtypes.CommissionRates{
Rate: sdk.NewDecWithPrec(6, 2), // 6%
MaxRate: sdk.NewDec(420),
MaxChangeRate: sdk.NewDec(420),
},
MinSelfDelegation: sdk.NewInt(1),
DelegatorAddress: testutil.AccAddress().String(),
ValidatorAddress: valAddr,
Pubkey: &codectypes.Any{},
Value: sdk.NewInt64Coin("unibi", 1),
},
&stakingtypes.MsgEditValidator{
Description: mockDescription,
ValidatorAddress: valAddr,
CommissionRate: commissionRatePointer, // 10%
MinSelfDelegation: nil,
},
}

createSadMsgs := func() []sdk.Msg {
sadMsgCreateVal := new(stakingtypes.MsgCreateValidator)
*sadMsgCreateVal = *(happyMsgs[0]).(*stakingtypes.MsgCreateValidator)
sadMsgCreateVal.Commission.Rate = sdk.NewDecWithPrec(26, 2)

sadMsgEditVal := new(stakingtypes.MsgEditValidator)
*sadMsgEditVal = *(happyMsgs[1]).(*stakingtypes.MsgEditValidator)
newCommissionRate := new(sdk.Dec)
*newCommissionRate = sdk.NewDecWithPrec(26, 2)
sadMsgEditVal.CommissionRate = newCommissionRate

return []sdk.Msg{
sadMsgCreateVal,
sadMsgEditVal,
}
}
sadMsgs := createSadMsgs()

for _, tc := range []struct {
name string
txMsgs []sdk.Msg
wantErr string
}{
{
name: "happy blank",
txMsgs: []sdk.Msg{},
wantErr: "",
},
{
name: "happy msgs",
txMsgs: []sdk.Msg{
happyMsgs[0],
happyMsgs[1],
},
wantErr: "",
},
{
name: "sad: max commission on create validator",
txMsgs: []sdk.Msg{
sadMsgs[0],
happyMsgs[1],
},
wantErr: ante.ErrMaxValidatorCommission.Error(),
},
{
name: "sad: max commission on edit validator",
txMsgs: []sdk.Msg{
happyMsgs[0],
sadMsgs[1],
},
wantErr: ante.ErrMaxValidatorCommission.Error(),
},
} {
s.T().Run(tc.name, func(t *testing.T) {
txGasCoins := sdk.NewCoins(
sdk.NewCoin("unibi", sdk.NewInt(1_000)),
sdk.NewCoin("utoken", sdk.NewInt(500)),
)

encCfg := app.MakeEncodingConfigAndRegister()
txBuilder, err := sdkclienttx.Factory{}.
WithFees(txGasCoins.String()).
WithChainID(s.ctx.ChainID()).
WithTxConfig(encCfg.TxConfig).
BuildUnsignedTx(tc.txMsgs...)
s.NoError(err)

anteDecorator := ante.AnteDecoratorStakingCommission{}
simulate := true
s.ctx, err = anteDecorator.AnteHandle(
s.ctx, txBuilder.GetTx(), simulate, nextAnteHandler,
)

if tc.wantErr != "" {
s.ErrorContains(err, tc.wantErr)
return
}
s.NoError(err)
})
}
}
24 changes: 24 additions & 0 deletions app/ante/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ante

import (
sdkerrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
)

var errorCodeIdx uint32 = 1

func registerError(errMsg string) *sdkerrors.Error {
errorCodeIdx += 1
return sdkerrors.Register("ante-nibiru", errorCodeIdx, errMsg)
}

// app/ante "sentinel" errors
var (
ErrOracleAnte = registerError("oracle ante error")
ErrMaxValidatorCommission = registerError("validator commission rate is above max")
)

func NewErrMaxValidatorCommission(gotCommission sdk.Dec) error {
return ErrMaxValidatorCommission.Wrapf(
"got (%s), max rate is (%s)", gotCommission, MAX_COMMISSION())
}
5 changes: 2 additions & 3 deletions app/ante/fixed_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package ante
import (
sdkerrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"

oracletypes "github.com/NibiruChain/nibiru/x/oracle/types"
)
Expand Down Expand Up @@ -41,13 +40,13 @@ func (gd EnsureSinglePostPriceMessageDecorator) AnteHandle(

if hasOracleVoteMsg && hasOraclePreVoteMsg {
if len(msgs) > 2 {
return ctx, sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction cannot have more than a single oracle vote and prevote message")
return ctx, sdkerrors.Wrap(ErrOracleAnte, "a transaction cannot have more than a single oracle vote and prevote message")
}

ctx = ctx.WithGasMeter(NewFixedGasMeter(OracleMessageGas))
} else if hasOraclePreVoteMsg || hasOracleVoteMsg {
if len(msgs) > 1 {
return ctx, sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages")
return ctx, sdkerrors.Wrap(ErrOracleAnte, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages")
}

ctx = ctx.WithGasMeter(NewFixedGasMeter(OracleMessageGas))
Expand Down
18 changes: 9 additions & 9 deletions app/ante/fixed_gas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package ante_test
import (
"testing"

"github.com/cosmos/cosmos-sdk/types/errors"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

sdkerrors "cosmossdk.io/errors"
sdkioerrors "cosmossdk.io/errors"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -68,7 +68,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
},
},
expectedGas: 1042,
expectedErr: sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
expectedErr: sdkioerrors.Wrap(ante.ErrOracleAnte, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
},
{
name: "Two messages in a transaction, one of them is an oracle vote message should fail (with MsgAggregateExchangeRatePrevote) permutation 2",
Expand All @@ -85,7 +85,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
},
},
expectedGas: 1042,
expectedErr: sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
expectedErr: sdkioerrors.Wrap(ante.ErrOracleAnte, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
},
{
name: "Two messages in a transaction, one of them is an oracle vote message should fail (with MsgAggregateExchangeRateVote)",
Expand All @@ -103,7 +103,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
},
},
expectedGas: 1042,
expectedErr: sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
expectedErr: sdkioerrors.Wrap(ante.ErrOracleAnte, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
},
{
name: "Two messages in a transaction, one of them is an oracle vote message should fail (with MsgAggregateExchangeRateVote) permutation 2",
Expand All @@ -121,7 +121,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
},
},
expectedGas: 1042,
expectedErr: sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
expectedErr: sdkioerrors.Wrap(ante.ErrOracleAnte, "a transaction that includes an oracle vote or prevote message cannot have more than those two messages"),
},
{
name: "Two messages in a transaction, one is oracle vote, the other oracle pre vote: should work with fixed price",
Expand Down Expand Up @@ -180,7 +180,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
},
},
expectedGas: 1042,
expectedErr: sdkerrors.Wrap(errors.ErrInvalidRequest, "a transaction cannot have more than a single oracle vote and prevote message"),
expectedErr: sdkioerrors.Wrap(ante.ErrOracleAnte, "a transaction cannot have more than a single oracle vote and prevote message"),
},
{
name: "Other two messages",
Expand All @@ -196,7 +196,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
Amount: sdk.NewCoins(sdk.NewInt64Coin(app.BondDenom, 200)),
},
},
expectedGas: 62504,
expectedGas: 62288,
expectedErr: nil,
},
}
Expand Down Expand Up @@ -249,7 +249,7 @@ func (suite *AnteTestSuite) TestOraclePostPriceTransactionsHaveFixedPrice() {
func (s *AnteTestSuite) ValidateTx(tx signing.Tx, t *testing.T) {
memoTx, ok := tx.(sdk.TxWithMemo)
if !ok {
s.Fail(sdkerrors.Wrap(errors.ErrTxDecode, "invalid transaction type").Error(), "memoTx: %t", memoTx)
s.Fail(sdkioerrors.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type").Error(), "memoTx: %t", memoTx)
}

params := s.app.AccountKeeper.GetParams(s.ctx)
Expand Down
6 changes: 4 additions & 2 deletions app/ante/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"

"github.com/NibiruChain/nibiru/app"
feeante "github.com/NibiruChain/nibiru/app/ante"
nibiruante "github.com/NibiruChain/nibiru/app/ante"
"github.com/NibiruChain/nibiru/x/common/testutil/genesis"
"github.com/NibiruChain/nibiru/x/common/testutil/testapp"
)
Expand All @@ -38,6 +38,7 @@ type AnteTestSuite struct {
// SetupTest setups a new test, with new app, context, and anteHandler.
func (suite *AnteTestSuite) SetupTest() {
// Set up base app and ctx
testapp.EnsureNibiruPrefix()
encodingConfig := genesis.TEST_ENCODING_CONFIG
suite.app = testapp.NewNibiruTestApp(app.NewDefaultGenesisState(encodingConfig.Marshaler))
chainId := "test-chain-id"
Expand Down Expand Up @@ -66,7 +67,8 @@ func (suite *AnteTestSuite) SetupTest() {
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ante.NewValidateMemoDecorator(suite.app.AccountKeeper),
feeante.NewPostPriceFixedPriceDecorator(),
nibiruante.NewPostPriceFixedPriceDecorator(),
nibiruante.AnteDecoratorStakingCommission{},
ante.NewConsumeGasForTxSizeDecorator(suite.app.AccountKeeper),
ante.NewDeductFeeDecorator(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper, nil), // Replace fee ante from cosmos auth with a custom one.

Expand Down
2 changes: 1 addition & 1 deletion x/common/testutil/cli/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ func New(logger Logger, baseDir string, cfg Config) (*Network, error) {
genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: balances.Sort()})
genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0))

commission, err := sdk.NewDecFromStr("0.5")
commission, err := sdk.NewDecFromStr("0.05")
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions x/tokenfactory/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
)

func (s *TestSuite) TestQueryModuleParams() {
res, err := s.queryClient.Params(s.GoCtx(), &types.QueryParamsRequest{})
res, err := s.querier.Params(s.GoCtx(), &types.QueryParamsRequest{})
s.NoError(err)
s.Equal(*res, types.DefaultModuleParams())
s.Equal(res.Params, types.DefaultModuleParams())
}
Loading

0 comments on commit 6c24125

Please sign in to comment.