Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(community): add MsgUpdateParams for governance #1745

Merged
merged 10 commits into from
Oct 11, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (metrics) [#1669] Add performance timing metrics to all Begin/EndBlockers
- (community) [#1704] Add module params
- (community) [#1706] Add disable inflation upgrade
- (community) [#1745] Enable params update via governance with `MsgUpdateParams`

### Bug Fixes

Expand Down Expand Up @@ -292,6 +293,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
large-scale simulations remotely using aws-batch

[#1745]: https://github.com/Kava-Labs/kava/pull/1745
[#1707]: https://github.com/Kava-Labs/kava/pull/1707
[#1706]: https://github.com/Kava-Labs/kava/pull/1706
[#1668]: https://github.com/Kava-Labs/kava/pull/1668
Expand Down
7 changes: 5 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,10 @@ var (
)

// Verify app interface at compile time
var _ servertypes.Application = (*App)(nil)
var _ servertypes.ApplicationQueryService = (*App)(nil)
var (
_ servertypes.Application = (*App)(nil)
_ servertypes.ApplicationQueryService = (*App)(nil)
)

// Options bundles several configuration params for an App.
type Options struct {
Expand Down Expand Up @@ -667,6 +669,7 @@ func NewApp(
&app.mintKeeper,
&app.kavadistKeeper,
app.stakingKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName), // Authority
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
)

app.incentiveKeeper = incentivekeeper.NewKeeper(
Expand Down
29 changes: 29 additions & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@
- [kava/community/v1beta1/tx.proto](#kava/community/v1beta1/tx.proto)
- [MsgFundCommunityPool](#kava.community.v1beta1.MsgFundCommunityPool)
- [MsgFundCommunityPoolResponse](#kava.community.v1beta1.MsgFundCommunityPoolResponse)
- [MsgUpdateParams](#kava.community.v1beta1.MsgUpdateParams)
- [MsgUpdateParamsResponse](#kava.community.v1beta1.MsgUpdateParamsResponse)

- [Msg](#kava.community.v1beta1.Msg)

Expand Down Expand Up @@ -3214,6 +3216,32 @@ MsgFundCommunityPoolResponse defines the Msg/FundCommunityPool response type.




<a name="kava.community.v1beta1.MsgUpdateParams"></a>

### MsgUpdateParams
MsgUpdateParams allows an account to update the community module parameters.


| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `authority` | [string](#string) | | authority is the address that controls the module (defaults to x/gov unless overwritten). |
| `params` | [Params](#kava.community.v1beta1.Params) | | params defines the x/distribution parameters to update. |






<a name="kava.community.v1beta1.MsgUpdateParamsResponse"></a>

### MsgUpdateParamsResponse
MsgUpdateParamsResponse defines the Msg/UpdateParams response type.





<!-- end messages -->

<!-- end enums -->
Expand All @@ -3229,6 +3257,7 @@ Msg defines the community Msg service.
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `FundCommunityPool` | [MsgFundCommunityPool](#kava.community.v1beta1.MsgFundCommunityPool) | [MsgFundCommunityPoolResponse](#kava.community.v1beta1.MsgFundCommunityPoolResponse) | FundCommunityPool defines a method to allow an account to directly fund the community module account. | |
| `UpdateParams` | [MsgUpdateParams](#kava.community.v1beta1.MsgUpdateParams) | [MsgUpdateParamsResponse](#kava.community.v1beta1.MsgUpdateParamsResponse) | UpdateParams defines a method to allow an account to update the community module parameters. | |

<!-- end services -->

Expand Down
2 changes: 2 additions & 0 deletions proto/kava/community/v1beta1/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ option go_package = "github.com/kava-labs/kava/x/community/types";

// Params defines the parameters of the community module.
message Params {
option (gogoproto.equal) = true;
drklee3 marked this conversation as resolved.
Show resolved Hide resolved

// upgrade_time_disable_inflation is the time at which to disable mint and kavadist module inflation.
// If set to 0, inflation will be disabled from block 1.
google.protobuf.Timestamp upgrade_time_disable_inflation = 1 [
Expand Down
18 changes: 18 additions & 0 deletions proto/kava/community/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package kava.community.v1beta1;
import "cosmos/base/v1beta1/coin.proto";
import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "kava/community/v1beta1/params.proto";

option go_package = "github.com/kava-labs/kava/x/community/types";
option (gogoproto.equal_all) = true;
Expand All @@ -12,6 +13,9 @@ option (gogoproto.equal_all) = true;
service Msg {
// FundCommunityPool defines a method to allow an account to directly fund the community module account.
rpc FundCommunityPool(MsgFundCommunityPool) returns (MsgFundCommunityPoolResponse);

// UpdateParams defines a method to allow an account to update the community module parameters.
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
}

// MsgFundCommunityPool allows an account to directly fund the community module account.
Expand All @@ -27,3 +31,17 @@ message MsgFundCommunityPool {

// MsgFundCommunityPoolResponse defines the Msg/FundCommunityPool response type.
message MsgFundCommunityPoolResponse {}

// MsgUpdateParams allows an account to update the community module parameters.
message MsgUpdateParams {
option (gogoproto.goproto_getters) = false;

// authority is the address that controls the module (defaults to x/gov unless overwritten).
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// params defines the x/distribution parameters to update.
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
Params params = 2 [(gogoproto.nullable) = false];
}

// MsgUpdateParamsResponse defines the Msg/UpdateParams response type.
message MsgUpdateParamsResponse {}
168 changes: 168 additions & 0 deletions tests/e2e/e2e_community_update_params_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package e2e_test

import (
"context"
"encoding/hex"
"time"

sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"

"github.com/kava-labs/kava/tests/e2e/testutil"
"github.com/kava-labs/kava/tests/util"
communitytypes "github.com/kava-labs/kava/x/community/types"
)

func (suite *IntegrationTestSuite) TestCommunityUpdateParams_NonAuthority() {
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
// ARRANGE
// setup kava account
funds := ukava(1e5) // .1 KAVA
kavaAcc := suite.Kava.NewFundedAccount("community-non-authority", sdk.NewCoins(funds))

gasLimit := int64(2e5)
fee := ukava(200)

msg := communitytypes.NewMsgUpdateParams(
kavaAcc.SdkAddress,
communitytypes.DefaultParams(),
)

// ACT
req := util.KavaMsgRequest{
Msgs: []sdk.Msg{&msg},
GasLimit: uint64(gasLimit),
FeeAmount: sdk.NewCoins(fee),
Memo: "this is a failure!",
}
res := kavaAcc.SignAndBroadcastKavaTx(req)

// ASSERT
_, err := util.WaitForSdkTxCommit(suite.Kava.Tx, res.Result.TxHash, 6*time.Second)
suite.Error(err)
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
}

func (suite *IntegrationTestSuite) TestCommunityUpdateParams_Authority() {
// ARRANGE
govParamsRes, err := suite.Kava.Gov.Params(context.Background(), &govv1.QueryParamsRequest{
ParamsType: govv1.ParamDeposit,
})
suite.NoError(err)

// Check initial params
communityParamsResInitial, err := suite.Kava.Community.Params(
context.Background(),
&communitytypes.QueryParamsRequest{},
)
suite.Require().NoError(err)

// setup kava account
// .1 KAVA + min deposit amount for proposal
funds := sdk.NewCoins(ukava(1e5)).Add(govParamsRes.DepositParams.MinDeposit...)
kavaAcc := suite.Kava.NewFundedAccount("community-update-params", funds)

gasLimit := int64(2e5)
fee := ukava(200)

upgradeTime := time.Now().Add(24 * time.Hour).UTC()

// 1. Proposal
updateParamsMsg := communitytypes.NewMsgUpdateParams(
authtypes.NewModuleAddress(govtypes.ModuleName), // authority
communitytypes.NewParams(
upgradeTime,
sdkmath.LegacyNewDec(1111), // stakingRewardsPerSecond
sdkmath.LegacyNewDec(2222), // upgradeTimeSetstakingRewardsPerSecond
),
)

// Make sure we're actually changing the params
suite.NotEqual(
updateParamsMsg.Params,
communityParamsResInitial.Params,
"new params should be different from existing",
)

proposalMsg, err := govv1.NewMsgSubmitProposal(
[]sdk.Msg{&updateParamsMsg},
govParamsRes.DepositParams.MinDeposit,
kavaAcc.SdkAddress.String(),
"community-update-params",
)
suite.NoError(err)

req := util.KavaMsgRequest{
Msgs: []sdk.Msg{proposalMsg},
GasLimit: uint64(gasLimit),
FeeAmount: sdk.NewCoins(fee),
Memo: "this is a proposal please accept me",
}
res := kavaAcc.SignAndBroadcastKavaTx(req)
suite.Require().NoError(res.Err)

// Wait for proposal to be submitted
txRes, err := util.WaitForSdkTxCommit(suite.Kava.Tx, res.Result.TxHash, 6*time.Second)
suite.Require().NoError(err)

// Parse tx response to get proposal id
var govRes govv1.MsgSubmitProposalResponse
suite.decodeTxMsgResponse(txRes, &govRes)

// 2. Vote for proposal from whale account
whale := suite.Kava.GetAccount(testutil.FundedAccountName)
voteMsg := govv1.NewMsgVote(
whale.SdkAddress,
govRes.ProposalId,
govv1.OptionYes,
"",
)

voteReq := util.KavaMsgRequest{
Msgs: []sdk.Msg{voteMsg},
GasLimit: uint64(gasLimit),
FeeAmount: sdk.NewCoins(fee),
Memo: "voting",
}
voteRes := whale.SignAndBroadcastKavaTx(voteReq)
suite.Require().NoError(voteRes.Err)

_, err = util.WaitForSdkTxCommit(suite.Kava.Tx, voteRes.Result.TxHash, 6*time.Second)
suite.Require().NoError(err)

// 3. Wait until proposal passes
suite.Require().Eventually(func() bool {
proposalRes, err := suite.Kava.Gov.Proposal(context.Background(), &govv1.QueryProposalRequest{
ProposalId: govRes.ProposalId,
})
suite.NoError(err)

return proposalRes.Proposal.Status == govv1.StatusPassed
}, 60*time.Second, 1*time.Second)

// Check parameters are updated
communityParamsRes, err := suite.Kava.Community.Params(
context.Background(),
&communitytypes.QueryParamsRequest{},
)
suite.Require().NoError(err)

suite.Equal(updateParamsMsg.Params, communityParamsRes.Params)
}

func (suite *IntegrationTestSuite) decodeTxMsgResponse(txRes *sdk.TxResponse, ptr codec.ProtoMarshaler) {
// convert txRes.Data hex string to bytes
txResBytes, err := hex.DecodeString(txRes.Data)
suite.Require().NoError(err)

// Unmarshal data to TxMsgData
var txMsgData sdk.TxMsgData
suite.Kava.EncodingConfig.Marshaler.MustUnmarshal(txResBytes, &txMsgData)
suite.T().Logf("txData.MsgResponses: %v", txMsgData.MsgResponses)

// Parse MsgResponse
suite.Kava.EncodingConfig.Marshaler.MustUnmarshal(txMsgData.MsgResponses[0].Value, ptr)
suite.Require().NoError(err)
}
3 changes: 3 additions & 0 deletions tests/e2e/testutil/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

"github.com/ethereum/go-ethereum"
Expand Down Expand Up @@ -55,6 +56,7 @@ type Chain struct {
Earn earntypes.QueryClient
Evm evmtypes.QueryClient
Evmutil evmutiltypes.QueryClient
Gov govv1types.QueryClient
Tm tmservice.ServiceClient
Tx txtypes.ServiceClient
Upgrade upgradetypes.QueryClient
Expand Down Expand Up @@ -91,6 +93,7 @@ func NewChain(t *testing.T, details *runner.ChainDetails, fundedAccountMnemonic
chain.Earn = earntypes.NewQueryClient(grpcConn)
chain.Evm = evmtypes.NewQueryClient(grpcConn)
chain.Evmutil = evmutiltypes.NewQueryClient(grpcConn)
chain.Gov = govv1types.NewQueryClient(grpcConn)
chain.Tm = tmservice.NewServiceClient(grpcConn)
chain.Tx = txtypes.NewServiceClient(grpcConn)
chain.Upgrade = upgradetypes.NewQueryClient(grpcConn)
Expand Down
11 changes: 11 additions & 0 deletions x/community/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ type Keeper struct {
kavadistKeeper types.KavadistKeeper
stakingKeeper types.StakingKeeper

// the address capable of executing a MsgUpdateParams message. Typically, this
// should be the x/gov module account.
authority sdk.AccAddress

legacyCommunityPoolAddress sdk.AccAddress
}

Expand All @@ -40,6 +44,7 @@ func NewKeeper(
mk types.MintKeeper,
kk types.KavadistKeeper,
sk types.StakingKeeper,
authority sdk.AccAddress,
) Keeper {
// ensure community module account is set
addr := ak.GetModuleAddress(types.ModuleAccountName)
Expand All @@ -64,10 +69,16 @@ func NewKeeper(
stakingKeeper: sk,
moduleAddress: addr,

authority: authority,
legacyCommunityPoolAddress: legacyAddr,
}
}

// GetAuthority returns the x/community module's authority.
func (k Keeper) GetAuthority() sdk.AccAddress {
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
return k.authority
}

// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+types.ModuleName)
Expand Down
27 changes: 27 additions & 0 deletions x/community/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package keeper
import (
"context"

"cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

"github.com/kava-labs/kava/x/community/types"
)
Expand Down Expand Up @@ -47,3 +49,28 @@ func (s msgServer) FundCommunityPool(goCtx context.Context, msg *types.MsgFundCo

return &types.MsgFundCommunityPoolResponse{}, nil
}

// UpdateParams handles UpdateParams msgs.
func (s msgServer) UpdateParams(
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
goCtx context.Context,
msg *types.MsgUpdateParams,
) (*types.MsgUpdateParamsResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if s.keeper.GetAuthority().String() != msg.Authority {
return nil, errors.Wrapf(
govtypes.ErrInvalidSigner,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's worth using an error defined in the community module (or a generic one like sdk.ErrUnauthorized) instead of the gov one?
Removing the govtypes import would avoid coupling to the gov module (making it easier to swap out gov for another implementation). Also the authority doesn't strictly need to be the gov module account but the error message implies it should be.
Downsides are that it would differ from the sdk modules, which use the gov error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I think for now we can be consistent with other modules -- if we switch out gov later then we should also be able to update the error response then, assuming a gov switchout is a significant breaking change

"invalid authority; expected %s, got %s",
s.keeper.GetAuthority(),
msg.Authority,
)
}

if err := msg.Params.Validate(); err != nil {
drklee3 marked this conversation as resolved.
Show resolved Hide resolved
return nil, errors.Wrap(types.ErrInvalidParams, err.Error())
}

s.keeper.SetParams(ctx, msg.Params)

return &types.MsgUpdateParamsResponse{}, nil
}
Loading
Loading