From f37321839b196030cd04151496c288a184e2a285 Mon Sep 17 00:00:00 2001 From: Robert Pirtle Date: Fri, 9 Dec 2022 13:24:35 -0800 Subject: [PATCH] add x/kavamint & x/community modules (#1400) * add `x/kavamint` module (#1376) * stub out the x/kavamint module * mint staking rewards in x/kavamint BeginBlocker * calculate cumulative inflation from x/kavamint * cleanup, abstract bond denom, grpc query tests * store & use previous block time to calculate mints * move test suite to testutil * add test of x/kavamint BeginBlocker * cleanup & comments from @DracoLi review * fix x/incentive integration test * remove rest client grpc test * add x/kavamint module spec * track previous block time in genesis * suggested cleanup from PR review * bugfix incorrect module account check * add x/community module (#1388) * add x/community module * refactor and setup fund-community-pool tx * remove unused features (params, genesis state) * test x/community query and message * update with master branch deps * updates from review * indicate deprecated methods on module (still needed to fulfill interface) * remove legacy querier * derive msg Route() and Type() from sdk.MsgTypeURL(&msg) * use module name for message Route() * mint community pool inflation in x/kavamint (#1389) * mint community pool inflation in x/kavamint * refactor x/kavamint abci inflation test * test inflation from x/kavamint * default kavamint rates to 0% * rename kavamint account name variables * panic if genesis kavamint previous block time not set * emit event on kavamint begin blocker * add kavamint year of inflation sanity check test * update fund_community_pool message event * add x/community spec * refactor inflation accumulation into single func * use new comm pool in kavadist multispend proposal (#1403) the x/community module houses the new community pool. this commit points the CommunityPoolMultiSpendProposal at the new community pool so that multispend proposals can continue to be processed once original fee pool funds are move to x/community * updates from proto lint changes * update earn proposals to use new community pool originally the CommunityPoolDepositProposal & CommunityPoolWithdrawProposal used the vanilla community pool fee pool. this commit updates them both to use the new x/community module account. * spec formatting update * add x/community & x/kavamint to internal testnet genesis * increase test coverage of x/kavamint * increase test coverage of x/community * use string type for sdk.Dec proto fields; disable generation of getters for genesis and params (and other store objects) * follow sdk conventions for params endpoint naming * add kavamint to swagger; update swagger & proto docs * remove legacy querier; touch up module to match others * reduce max minting rate; refactor genesis to allow nil/zero block time; repace proto stringers and remove getters; keep sdk.Dec values strings in protos; increase validation of sdk.Dec values in params; increase coverage; add module account permission checks; ensure import and export of genesis does not change state * fix scaling of rate values -- should be yearly rates * revert key change * fix typo in test name * fix regression in running tests -- rename method Co-authored-by: Nick DeLuca --- app/app.go | 43 +- app/test_common.go | 35 +- ci/env/kava-internal-testnet/genesis.json | 18 +- client/docs/config.json | 17 + client/docs/swagger-ui/swagger.yaml | 138 +++ docs/core/proto-docs.md | 270 ++++++ proto/kava/community/v1beta1/query.proto | 27 + proto/kava/community/v1beta1/tx.proto | 29 + proto/kava/kavamint/v1beta1/genesis.proto | 23 + proto/kava/kavamint/v1beta1/kavamint.proto | 26 + proto/kava/kavamint/v1beta1/query.proto | 44 + x/community/abci.go | 12 + x/community/client/cli/query.go | 55 ++ x/community/client/cli/tx.go | 66 ++ x/community/genesis.go | 19 + x/community/genesis_test.go | 44 + x/community/keeper/grpc_query.go | 26 + x/community/keeper/grpc_query_test.go | 66 ++ x/community/keeper/keeper.go | 50 + x/community/keeper/keeper_test.go | 63 ++ x/community/keeper/msg_server.go | 49 + x/community/keeper/msg_server_test.go | 111 +++ x/community/module.go | 146 +++ x/community/module_test.go | 22 + x/community/spec/01_concepts.md | 19 + x/community/spec/02_state.md | 7 + x/community/spec/03_messages.md | 17 + x/community/spec/04_events.md | 18 + x/community/spec/README.md | 19 + x/community/testutil/main.go | 41 + x/community/types/codec.go | 36 + x/community/types/events.go | 7 + x/community/types/expected_keepers.go | 20 + x/community/types/keys.go | 12 + x/community/types/msg.go | 56 ++ x/community/types/msg_test.go | 93 ++ x/community/types/query.pb.go | 550 +++++++++++ x/community/types/query.pb.gw.go | 153 +++ x/community/types/tx.pb.go | 640 +++++++++++++ x/earn/keeper/keeper.go | 9 +- x/earn/keeper/proposal_handler.go | 20 +- x/earn/keeper/proposal_handler_test.go | 25 +- x/earn/types/expected_keepers.go | 9 - .../rewards_earn_staking_integration_test.go | 3 + x/incentive/testutil/kavamint_builder.go | 40 + x/kavadist/keeper/keeper.go | 16 +- x/kavadist/keeper/proposal_handler.go | 2 +- x/kavadist/keeper/proposal_handler_test.go | 23 +- x/kavadist/types/expected_keepers.go | 6 +- x/kavamint/abci.go | 75 ++ x/kavamint/abci_test.go | 196 ++++ x/kavamint/client/cli/query.go | 89 ++ x/kavamint/genesis.go | 38 + x/kavamint/genesis_test.go | 117 +++ x/kavamint/keeper/grpc_query.go | 26 + x/kavamint/keeper/grpc_query_test.go | 121 +++ x/kavamint/keeper/inflation.go | 54 ++ x/kavamint/keeper/inflation_test.go | 142 +++ x/kavamint/keeper/keeper.go | 178 ++++ x/kavamint/keeper/keeper_test.go | 53 ++ x/kavamint/module.go | 152 +++ x/kavamint/module_test.go | 31 + x/kavamint/spec/01_concepts.md | 45 + x/kavamint/spec/02_state.md | 36 + x/kavamint/spec/03_messages.md | 7 + x/kavamint/spec/04_events.md | 17 + x/kavamint/spec/05_params.md | 12 + x/kavamint/spec/06_begin_block.md | 49 + x/kavamint/spec/README.md | 21 + x/kavamint/testutil/suite.go | 61 ++ x/kavamint/types/events.go | 12 + x/kavamint/types/expected_keepers.go | 26 + x/kavamint/types/genesis.go | 32 + x/kavamint/types/genesis.pb.go | 372 ++++++++ x/kavamint/types/genesis_test.go | 149 +++ x/kavamint/types/kavamint.pb.go | 373 ++++++++ x/kavamint/types/keys.go | 18 + x/kavamint/types/params.go | 87 ++ x/kavamint/types/params_test.go | 297 ++++++ x/kavamint/types/query.pb.go | 870 ++++++++++++++++++ x/kavamint/types/query.pb.gw.go | 218 +++++ 81 files changed, 7147 insertions(+), 67 deletions(-) create mode 100644 proto/kava/community/v1beta1/query.proto create mode 100644 proto/kava/community/v1beta1/tx.proto create mode 100644 proto/kava/kavamint/v1beta1/genesis.proto create mode 100644 proto/kava/kavamint/v1beta1/kavamint.proto create mode 100644 proto/kava/kavamint/v1beta1/query.proto create mode 100644 x/community/abci.go create mode 100644 x/community/client/cli/query.go create mode 100644 x/community/client/cli/tx.go create mode 100644 x/community/genesis.go create mode 100644 x/community/genesis_test.go create mode 100644 x/community/keeper/grpc_query.go create mode 100644 x/community/keeper/grpc_query_test.go create mode 100644 x/community/keeper/keeper.go create mode 100644 x/community/keeper/keeper_test.go create mode 100644 x/community/keeper/msg_server.go create mode 100644 x/community/keeper/msg_server_test.go create mode 100644 x/community/module.go create mode 100644 x/community/module_test.go create mode 100644 x/community/spec/01_concepts.md create mode 100644 x/community/spec/02_state.md create mode 100644 x/community/spec/03_messages.md create mode 100644 x/community/spec/04_events.md create mode 100644 x/community/spec/README.md create mode 100644 x/community/testutil/main.go create mode 100644 x/community/types/codec.go create mode 100644 x/community/types/events.go create mode 100644 x/community/types/expected_keepers.go create mode 100644 x/community/types/keys.go create mode 100644 x/community/types/msg.go create mode 100644 x/community/types/msg_test.go create mode 100644 x/community/types/query.pb.go create mode 100644 x/community/types/query.pb.gw.go create mode 100644 x/community/types/tx.pb.go create mode 100644 x/incentive/testutil/kavamint_builder.go create mode 100644 x/kavamint/abci.go create mode 100644 x/kavamint/abci_test.go create mode 100644 x/kavamint/client/cli/query.go create mode 100644 x/kavamint/genesis.go create mode 100644 x/kavamint/genesis_test.go create mode 100644 x/kavamint/keeper/grpc_query.go create mode 100644 x/kavamint/keeper/grpc_query_test.go create mode 100644 x/kavamint/keeper/inflation.go create mode 100644 x/kavamint/keeper/inflation_test.go create mode 100644 x/kavamint/keeper/keeper.go create mode 100644 x/kavamint/keeper/keeper_test.go create mode 100644 x/kavamint/module.go create mode 100644 x/kavamint/module_test.go create mode 100644 x/kavamint/spec/01_concepts.md create mode 100644 x/kavamint/spec/02_state.md create mode 100644 x/kavamint/spec/03_messages.md create mode 100644 x/kavamint/spec/04_events.md create mode 100644 x/kavamint/spec/05_params.md create mode 100644 x/kavamint/spec/06_begin_block.md create mode 100644 x/kavamint/spec/README.md create mode 100644 x/kavamint/testutil/suite.go create mode 100644 x/kavamint/types/events.go create mode 100644 x/kavamint/types/expected_keepers.go create mode 100644 x/kavamint/types/genesis.go create mode 100644 x/kavamint/types/genesis.pb.go create mode 100644 x/kavamint/types/genesis_test.go create mode 100644 x/kavamint/types/kavamint.pb.go create mode 100644 x/kavamint/types/keys.go create mode 100644 x/kavamint/types/params.go create mode 100644 x/kavamint/types/params_test.go create mode 100644 x/kavamint/types/query.pb.go create mode 100644 x/kavamint/types/query.pb.gw.go diff --git a/app/app.go b/app/app.go index d853d1d9..c60df3fd 100644 --- a/app/app.go +++ b/app/app.go @@ -106,6 +106,9 @@ import ( committeeclient "github.com/kava-labs/kava/x/committee/client" committeekeeper "github.com/kava-labs/kava/x/committee/keeper" committeetypes "github.com/kava-labs/kava/x/committee/types" + "github.com/kava-labs/kava/x/community" + communitykeeper "github.com/kava-labs/kava/x/community/keeper" + communitytypes "github.com/kava-labs/kava/x/community/types" earn "github.com/kava-labs/kava/x/earn" earnclient "github.com/kava-labs/kava/x/earn/client" earnkeeper "github.com/kava-labs/kava/x/earn/keeper" @@ -126,6 +129,9 @@ import ( kavadistclient "github.com/kava-labs/kava/x/kavadist/client" kavadistkeeper "github.com/kava-labs/kava/x/kavadist/keeper" kavadisttypes "github.com/kava-labs/kava/x/kavadist/types" + "github.com/kava-labs/kava/x/kavamint" + kavamintkeeper "github.com/kava-labs/kava/x/kavamint/keeper" + kavaminttypes "github.com/kava-labs/kava/x/kavamint/types" "github.com/kava-labs/kava/x/liquid" liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper" liquidtypes "github.com/kava-labs/kava/x/liquid/types" @@ -202,6 +208,8 @@ var ( liquid.AppModuleBasic{}, earn.AppModuleBasic{}, router.AppModuleBasic{}, + kavamint.AppModuleBasic{}, + community.AppModuleBasic{}, ) // module account permissions @@ -229,6 +237,8 @@ var ( liquidtypes.ModuleAccountName: {authtypes.Minter, authtypes.Burner}, earntypes.ModuleAccountName: nil, kavadisttypes.FundModuleAccount: nil, + kavaminttypes.ModuleAccountName: {authtypes.Minter}, + communitytypes.ModuleName: nil, } ) @@ -300,6 +310,8 @@ type App struct { liquidKeeper liquidkeeper.Keeper earnKeeper earnkeeper.Keeper routerKeeper routerkeeper.Keeper + kavamintKeeper kavamintkeeper.Keeper + communityKeeper communitykeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -353,7 +365,7 @@ func NewApp( issuancetypes.StoreKey, bep3types.StoreKey, pricefeedtypes.StoreKey, swaptypes.StoreKey, cdptypes.StoreKey, hardtypes.StoreKey, committeetypes.StoreKey, incentivetypes.StoreKey, evmutiltypes.StoreKey, - savingstypes.StoreKey, earntypes.StoreKey, + savingstypes.StoreKey, earntypes.StoreKey, kavaminttypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientKey) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) @@ -399,6 +411,7 @@ func NewApp( evmSubspace := app.paramsKeeper.Subspace(evmtypes.ModuleName) evmutilSubspace := app.paramsKeeper.Subspace(evmutiltypes.ModuleName) earnSubspace := app.paramsKeeper.Subspace(earntypes.ModuleName) + kavamintSubspace := app.paramsKeeper.Subspace(kavaminttypes.ModuleName) bApp.SetParamStore( app.paramsKeeper.Subspace(baseapp.Paramspace).WithKeyTable(paramskeeper.ConsensusParamsKeyTable()), @@ -511,13 +524,17 @@ func NewApp( app.evmutilKeeper.SetEvmKeeper(app.evmKeeper) + app.communityKeeper = communitykeeper.NewKeeper( + app.accountKeeper, + app.bankKeeper, + ) app.kavadistKeeper = kavadistkeeper.NewKeeper( appCodec, keys[kavadisttypes.StoreKey], kavadistSubspace, app.bankKeeper, app.accountKeeper, - app.distrKeeper, + app.communityKeeper, app.loadBlockedMaccAddrs(), ) @@ -617,7 +634,17 @@ func NewApp( &app.liquidKeeper, &hardKeeper, &savingsKeeper, - app.distrKeeper, + communitytypes.ModuleAccountName, + ) + app.kavamintKeeper = kavamintkeeper.NewKeeper( + appCodec, + keys[kavaminttypes.StoreKey], + kavamintSubspace, + app.stakingKeeper, + app.accountKeeper, + app.bankKeeper, + authtypes.FeeCollectorName, // same fee collector as vanilla sdk + communitytypes.ModuleAccountName, ) app.incentiveKeeper = incentivekeeper.NewKeeper( @@ -739,6 +766,8 @@ func NewApp( liquid.NewAppModule(app.liquidKeeper), earn.NewAppModule(app.earnKeeper, app.accountKeeper, app.bankKeeper), router.NewAppModule(app.routerKeeper), + kavamint.NewAppModule(appCodec, app.kavamintKeeper, app.accountKeeper), + community.NewAppModule(app.communityKeeper, app.accountKeeper), ) // Warning: Some begin blockers must run before others. Ensure the dependencies are understood before modifying this list. @@ -751,6 +780,9 @@ func NewApp( // Run before to ensure params are updated together before state changes. committeetypes.ModuleName, minttypes.ModuleName, + // Kavamint registers with the vanilla mint module. + // Must be run before distribution module in order to generate block staking rewards. + kavaminttypes.ModuleName, distrtypes.ModuleName, // During begin block slashing happens after distr.BeginBlocker so that // there is nothing left over in the validator fee pool, so as to keep the @@ -761,6 +793,7 @@ func NewApp( feemarkettypes.ModuleName, evmtypes.ModuleName, kavadisttypes.ModuleName, + communitytypes.ModuleName, // Auction begin blocker will close out expired auctions and pay debt back to cdp. // It should be run before cdp begin blocker which cancels out debt with stable and starts more auctions. auctiontypes.ModuleName, @@ -829,6 +862,8 @@ func NewApp( liquidtypes.ModuleName, earntypes.ModuleName, routertypes.ModuleName, + kavaminttypes.ModuleName, + communitytypes.ModuleName, ) // Warning: Some init genesis methods must run before others. Ensure the dependencies are understood before modifying this list @@ -841,6 +876,7 @@ func NewApp( slashingtypes.ModuleName, // iterates over validators, run after staking govtypes.ModuleName, minttypes.ModuleName, + kavaminttypes.ModuleName, ibchost.ModuleName, evidencetypes.ModuleName, authz.ModuleName, @@ -860,6 +896,7 @@ func NewApp( committeetypes.ModuleName, evmutiltypes.ModuleName, earntypes.ModuleName, + communitytypes.ModuleName, genutiltypes.ModuleName, // runs arbitrary txs included in genisis state, so run after modules have been initialized crisistypes.ModuleName, // runs the invariants at genesis, should run after other modules // Add all remaining modules with an empty InitGenesis below since cosmos 0.45.0 requires it diff --git a/app/test_common.go b/app/test_common.go index 4e511384..db02527d 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -22,6 +23,7 @@ import ( paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" @@ -34,12 +36,14 @@ import ( bep3keeper "github.com/kava-labs/kava/x/bep3/keeper" cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper" committeekeeper "github.com/kava-labs/kava/x/committee/keeper" + communitykeeper "github.com/kava-labs/kava/x/community/keeper" earnkeeper "github.com/kava-labs/kava/x/earn/keeper" evmutilkeeper "github.com/kava-labs/kava/x/evmutil/keeper" hardkeeper "github.com/kava-labs/kava/x/hard/keeper" incentivekeeper "github.com/kava-labs/kava/x/incentive/keeper" issuancekeeper "github.com/kava-labs/kava/x/issuance/keeper" kavadistkeeper "github.com/kava-labs/kava/x/kavadist/keeper" + kavamintkeeper "github.com/kava-labs/kava/x/kavamint/keeper" liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper" pricefeedkeeper "github.com/kava-labs/kava/x/pricefeed/keeper" routerkeeper "github.com/kava-labs/kava/x/router/keeper" @@ -65,7 +69,7 @@ var ( // However this leads to a lot of duplicated logic similar to InitGenesis methods. // So TestApp.InitializeFromGenesisStates() will call InitGenesis with the default genesis state. // and TestApp.InitializeFromGenesisStates(authState, cdpState) will do the same but overwrite the auth and cdp sections of the default genesis state -// Creating the genesis states can be combersome, but helper methods can make it easier such as NewAuthGenStateFromAccounts below. +// Creating the genesis states can be cumbersome, but helper methods can make it easier such as NewAuthGenStateFromAccounts below. type TestApp struct { App } @@ -117,6 +121,8 @@ func (tApp TestApp) GetFeeMarketKeeper() feemarketkeeper.Keeper { return tApp.fe func (tApp TestApp) GetLiquidKeeper() liquidkeeper.Keeper { return tApp.liquidKeeper } func (tApp TestApp) GetEarnKeeper() earnkeeper.Keeper { return tApp.earnKeeper } func (tApp TestApp) GetRouterKeeper() routerkeeper.Keeper { return tApp.routerKeeper } +func (tApp TestApp) GetKavamintKeeper() kavamintkeeper.Keeper { return tApp.kavamintKeeper } +func (tApp TestApp) GetCommunityKeeper() communitykeeper.Keeper { return tApp.communityKeeper } func (tApp TestApp) GetKeys() map[string]*sdk.KVStoreKey { return tApp.keys @@ -197,6 +203,13 @@ func (tApp TestApp) CheckBalance(t *testing.T, ctx sdk.Context, owner sdk.AccAdd require.Equal(t, expectedCoins, coins) } +// GetModuleAccountBalance gets the current balance of the denom for a module account +func (tApp TestApp) GetModuleAccountBalance(ctx sdk.Context, moduleName string, denom string) sdk.Int { + moduleAcc := tApp.accountKeeper.GetModuleAccount(ctx, moduleName) + balance := tApp.bankKeeper.GetBalance(ctx, moduleAcc.GetAddress(), denom) + return balance.Amount +} + // FundAccount is a utility function that funds an account by minting and sending the coins to the address. func (tApp TestApp) FundAccount(ctx sdk.Context, addr sdk.AccAddress, amounts sdk.Coins) error { if err := tApp.bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { @@ -220,6 +233,26 @@ func (tApp TestApp) FundModuleAccount(ctx sdk.Context, recipientMod string, amou return tApp.bankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, recipientMod, amounts) } +// CreateNewUnbondedValidator creates a new validator in the staking module. +// New validators are unbonded until the end blocker is run. +func (tApp TestApp) CreateNewUnbondedValidator(ctx sdk.Context, valAddress sdk.ValAddress, selfDelegation sdk.Int) error { + msg, err := stakingtypes.NewMsgCreateValidator( + valAddress, + ed25519.GenPrivKey().PubKey(), + sdk.NewCoin(tApp.stakingKeeper.BondDenom(ctx), selfDelegation), + stakingtypes.Description{}, + stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()), + sdk.NewInt(1e6), + ) + if err != nil { + return err + } + + msgServer := stakingkeeper.NewMsgServerImpl(tApp.stakingKeeper) + _, err = msgServer.CreateValidator(sdk.WrapSDKContext(ctx), msg) + return err +} + // GeneratePrivKeyAddressPairsFromRand generates (deterministically) a total of n private keys and addresses. func GeneratePrivKeyAddressPairs(n int) (keys []cryptotypes.PrivKey, addrs []sdk.AccAddress) { r := rand.New(rand.NewSource(12345)) // make the generation deterministic diff --git a/ci/env/kava-internal-testnet/genesis.json b/ci/env/kava-internal-testnet/genesis.json index 578c87f1..1bdf3b7b 100644 --- a/ci/env/kava-internal-testnet/genesis.json +++ b/ci/env/kava-internal-testnet/genesis.json @@ -933,6 +933,9 @@ "proposals": [], "votes": [] }, + "community": { + "params": {} + }, "crisis": { "constant_fee": { "denom": "ukava", @@ -941,7 +944,7 @@ }, "distribution": { "params": { - "community_tax": "0.020000000000000000", + "community_tax": "0.000000000000000000", "base_proposer_reward": "0.010000000000000000", "bonus_proposer_reward": "0.040000000000000000", "withdraw_addr_enabled": true @@ -2306,19 +2309,26 @@ }, "previous_block_time": "1970-01-01T00:00:01Z" }, + "kavamint": { + "params": { + "community_pool_inflation": "0.200000000000000000", + "staking_rewards_apy": "0.150000000000000000" + }, + "previous_block_time": "2022-05-25T17:00:00Z" + }, "liquid": { "params": {} }, "mint": { "minter": { - "inflation": "0.130000000000000000", + "inflation": "0.000000000000000000", "annual_provisions": "0.000000000000000000" }, "params": { "mint_denom": "ukava", "inflation_rate_change": "0.130000000000000000", - "inflation_max": "0.200000000000000000", - "inflation_min": "0.070000000000000000", + "inflation_max": "0.000000000000000000", + "inflation_min": "0.000000000000000000", "goal_bonded": "0.670000000000000000", "blocks_per_year": "6311520" } diff --git a/client/docs/config.json b/client/docs/config.json index 6319e6f6..0e692c8a 100644 --- a/client/docs/config.json +++ b/client/docs/config.json @@ -216,6 +216,23 @@ ] } }, + { + "url": "./out/swagger/kava/kavamint/v1beta1/query.swagger.json", + "tags": { + "rename": { + "Query": "Kavamint" + } + }, + "operationIds": { + "rename": [ + { + "type": "regex", + "from": "(.*)", + "to": "Kavamint$1" + } + ] + } + }, { "url": "./client/docs/cosmos-swagger.yml", "dereference": { diff --git a/client/docs/swagger-ui/swagger.yaml b/client/docs/swagger-ui/swagger.yaml index 0b6c9e8d..52c8a009 100644 --- a/client/docs/swagger-ui/swagger.yaml +++ b/client/docs/swagger-ui/swagger.yaml @@ -12033,6 +12033,101 @@ paths: format: byte tags: - Savings + /kava/kavamint/v1beta1/inflation: + get: + summary: >- + Inflation queries x/kavamint for the overall cumulative inflation rate + of KAVA. + operationId: KavamintInflation + responses: + '200': + description: A successful response. + schema: + type: object + properties: + inflation: + type: string + title: |- + inflation is the current minting inflation value. + example "0.990000000000000000" - 99% inflation + description: >- + QueryInflationResponse is the response type for the + Query/Inflation RPC method. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Kavamint + /kava/kavamint/v1beta1/params: + get: + summary: Params queries the parameters of x/kavamint module. + operationId: KavamintParams + responses: + '200': + description: A successful response. + schema: + type: object + properties: + params: + type: object + properties: + community_pool_inflation: + type: string + description: >- + yearly inflation of total token supply minted to the + community pool. + staking_rewards_apy: + type: string + description: >- + yearly inflation of bonded tokens minted for staking + rewards to validators. + title: Params wraps the governance parameters for the kavamint module + description: >- + QueryParamsResponse defines the response type for querying + x/kavamint parameters. + default: + description: An unexpected error response. + schema: + type: object + properties: + error: + type: string + code: + type: integer + format: int32 + message: + type: string + details: + type: array + items: + type: object + properties: + type_url: + type: string + value: + type: string + format: byte + tags: + - Kavamint /node_info: get: description: Information about the connected node @@ -57671,6 +57766,49 @@ definitions: description: >- TotalSupplyResponse defines the response type for the Query/TotalSupply method. + kava.kavamint.v1beta1.Params: + type: object + properties: + community_pool_inflation: + type: string + description: yearly inflation of total token supply minted to the community pool. + staking_rewards_apy: + type: string + description: >- + yearly inflation of bonded tokens minted for staking rewards to + validators. + title: Params wraps the governance parameters for the kavamint module + kava.kavamint.v1beta1.QueryInflationResponse: + type: object + properties: + inflation: + type: string + title: |- + inflation is the current minting inflation value. + example "0.990000000000000000" - 99% inflation + description: >- + QueryInflationResponse is the response type for the Query/Inflation RPC + method. + kava.kavamint.v1beta1.QueryParamsResponse: + type: object + properties: + params: + type: object + properties: + community_pool_inflation: + type: string + description: >- + yearly inflation of total token supply minted to the community + pool. + staking_rewards_apy: + type: string + description: >- + yearly inflation of bonded tokens minted for staking rewards to + validators. + title: Params wraps the governance parameters for the kavamint module + description: >- + QueryParamsResponse defines the response type for querying x/kavamint + parameters. CheckTxResult: type: object properties: diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 21cc8bae..25798639 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -178,6 +178,18 @@ - [Msg](#kava.committee.v1beta1.Msg) +- [kava/community/v1beta1/query.proto](#kava/community/v1beta1/query.proto) + - [QueryBalanceRequest](#kava.community.v1beta1.QueryBalanceRequest) + - [QueryBalanceResponse](#kava.community.v1beta1.QueryBalanceResponse) + + - [Query](#kava.community.v1beta1.Query) + +- [kava/community/v1beta1/tx.proto](#kava/community/v1beta1/tx.proto) + - [MsgFundCommunityPool](#kava.community.v1beta1.MsgFundCommunityPool) + - [MsgFundCommunityPoolResponse](#kava.community.v1beta1.MsgFundCommunityPoolResponse) + + - [Msg](#kava.community.v1beta1.Msg) + - [kava/earn/v1beta1/strategy.proto](#kava/earn/v1beta1/strategy.proto) - [StrategyType](#kava.earn.v1beta1.StrategyType) @@ -405,6 +417,20 @@ - [Query](#kava.kavadist.v1beta1.Query) +- [kava/kavamint/v1beta1/kavamint.proto](#kava/kavamint/v1beta1/kavamint.proto) + - [Params](#kava.kavamint.v1beta1.Params) + +- [kava/kavamint/v1beta1/genesis.proto](#kava/kavamint/v1beta1/genesis.proto) + - [GenesisState](#kava.kavamint.v1beta1.GenesisState) + +- [kava/kavamint/v1beta1/query.proto](#kava/kavamint/v1beta1/query.proto) + - [QueryInflationRequest](#kava.kavamint.v1beta1.QueryInflationRequest) + - [QueryInflationResponse](#kava.kavamint.v1beta1.QueryInflationResponse) + - [QueryParamsRequest](#kava.kavamint.v1beta1.QueryParamsRequest) + - [QueryParamsResponse](#kava.kavamint.v1beta1.QueryParamsResponse) + + - [Query](#kava.kavamint.v1beta1.Query) + - [kava/liquid/v1beta1/query.proto](#kava/liquid/v1beta1/query.proto) - [QueryDelegatedBalanceRequest](#kava.liquid.v1beta1.QueryDelegatedBalanceRequest) - [QueryDelegatedBalanceResponse](#kava.liquid.v1beta1.QueryDelegatedBalanceResponse) @@ -2812,6 +2838,109 @@ Msg defines the committee Msg service + +

Top

+ +## kava/community/v1beta1/query.proto + + + + + +### QueryBalanceRequest +QueryBalanceRequest defines the request type for querying x/community balance. + + + + + + + + +### QueryBalanceResponse +QueryBalanceResponse defines the response type for querying x/community balance. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `coins` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | | + + + + + + + + + + + + + + +### Query +Query defines the gRPC querier service for x/community. + +| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | +| ----------- | ------------ | ------------- | ------------| ------- | -------- | +| `Balance` | [QueryBalanceRequest](#kava.community.v1beta1.QueryBalanceRequest) | [QueryBalanceResponse](#kava.community.v1beta1.QueryBalanceResponse) | Balance queries the balance of all coins of x/community module. | GET|/kava/community/v1beta1/balance| + + + + + + +

Top

+ +## kava/community/v1beta1/tx.proto + + + + + +### MsgFundCommunityPool +MsgFundCommunityPool allows an account to directly fund the community module account. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | | +| `depositor` | [string](#string) | | | + + + + + + + + +### MsgFundCommunityPoolResponse +MsgFundCommunityPoolResponse defines the Msg/FundCommunityPool response type. + + + + + + + + + + + + + + +### Msg +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. | | + + + + +

Top

@@ -5738,6 +5867,147 @@ Query defines the gRPC querier service. + +

Top

+ +## kava/kavamint/v1beta1/kavamint.proto + + + + + +### Params +Params wraps the governance parameters for the kavamint module + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `community_pool_inflation` | [string](#string) | | yearly inflation of total token supply minted to the community pool. | +| `staking_rewards_apy` | [string](#string) | | yearly inflation of bonded tokens minted for staking rewards to validators. | + + + + + + + + + + + + + + + + +

Top

+ +## kava/kavamint/v1beta1/genesis.proto + + + + + +### GenesisState +GenesisState defines the kavamint module's genesis state. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `params` | [Params](#kava.kavamint.v1beta1.Params) | | Params defines all the parameters of the module. | +| `previous_block_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | PreviousBlockTime holds the last last time tokens were minted. On first block, tokens will be minted for total number of seconds passed since this time. | + + + + + + + + + + + + + + + + +

Top

+ +## kava/kavamint/v1beta1/query.proto + + + + + +### QueryInflationRequest +QueryInflationRequest is the request type for the Query/Inflation RPC method. + + + + + + + + +### QueryInflationResponse +QueryInflationResponse is the response type for the Query/Inflation RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `inflation` | [string](#string) | | inflation is the current minting inflation value. example "0.990000000000000000" - 99% inflation | + + + + + + + + +### QueryParamsRequest +QueryParamsRequest defines the request type for querying x/kavamint parameters. + + + + + + + + +### QueryParamsResponse +QueryParamsResponse defines the response type for querying x/kavamint parameters. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `params` | [Params](#kava.kavamint.v1beta1.Params) | | | + + + + + + + + + + + + + + +### Query +Query defines the gRPC querier service for kavamint. + +| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | +| ----------- | ------------ | ------------- | ------------| ------- | -------- | +| `Params` | [QueryParamsRequest](#kava.kavamint.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#kava.kavamint.v1beta1.QueryParamsResponse) | Params queries the parameters of x/kavamint module. | GET|/kava/kavamint/v1beta1/params| +| `Inflation` | [QueryInflationRequest](#kava.kavamint.v1beta1.QueryInflationRequest) | [QueryInflationResponse](#kava.kavamint.v1beta1.QueryInflationResponse) | Inflation queries x/kavamint for the overall cumulative inflation rate of KAVA. | GET|/kava/kavamint/v1beta1/inflation| + + + + +

Top

diff --git a/proto/kava/community/v1beta1/query.proto b/proto/kava/community/v1beta1/query.proto new file mode 100644 index 00000000..ad33cedb --- /dev/null +++ b/proto/kava/community/v1beta1/query.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; +package kava.community.v1beta1; + +import "cosmos/base/v1beta1/coin.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/kava-labs/kava/x/community/types"; + +// Query defines the gRPC querier service for x/community. +service Query { + // Balance queries the balance of all coins of x/community module. + rpc Balance(QueryBalanceRequest) returns (QueryBalanceResponse) { + option (google.api.http).get = "/kava/community/v1beta1/balance"; + } +} + +// QueryBalanceRequest defines the request type for querying x/community balance. +message QueryBalanceRequest {} + +// QueryBalanceResponse defines the response type for querying x/community balance. +message QueryBalanceResponse { + repeated cosmos.base.v1beta1.Coin coins = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; +} diff --git a/proto/kava/community/v1beta1/tx.proto b/proto/kava/community/v1beta1/tx.proto new file mode 100644 index 00000000..143447e1 --- /dev/null +++ b/proto/kava/community/v1beta1/tx.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; +package kava.community.v1beta1; + +import "cosmos/base/v1beta1/coin.proto"; +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; + +option go_package = "github.com/kava-labs/kava/x/community/types"; +option (gogoproto.equal_all) = true; + +// Msg defines the community Msg service. +service Msg { + // FundCommunityPool defines a method to allow an account to directly fund the community module account. + rpc FundCommunityPool(MsgFundCommunityPool) returns (MsgFundCommunityPoolResponse); +} + +// MsgFundCommunityPool allows an account to directly fund the community module account. +message MsgFundCommunityPool { + option (gogoproto.goproto_getters) = false; + + repeated cosmos.base.v1beta1.Coin amount = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" + ]; + string depositor = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgFundCommunityPoolResponse defines the Msg/FundCommunityPool response type. +message MsgFundCommunityPoolResponse {} diff --git a/proto/kava/kavamint/v1beta1/genesis.proto b/proto/kava/kavamint/v1beta1/genesis.proto new file mode 100644 index 00000000..f7bba2eb --- /dev/null +++ b/proto/kava/kavamint/v1beta1/genesis.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +package kava.kavamint.v1beta1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/timestamp.proto"; +import "kava/kavamint/v1beta1/kavamint.proto"; + +option go_package = "github.com/kava-labs/kava/x/kavamint/types"; + +// GenesisState defines the kavamint module's genesis state. +message GenesisState { + option (gogoproto.goproto_getters) = false; + + // Params defines all the parameters of the module. + Params params = 1 [(gogoproto.nullable) = false]; + + // PreviousBlockTime holds the last last time tokens were minted. + // On first block, tokens will be minted for total number of seconds passed since this time. + google.protobuf.Timestamp previous_block_time = 2 [ + (gogoproto.stdtime) = true, + (gogoproto.nullable) = false + ]; +} diff --git a/proto/kava/kavamint/v1beta1/kavamint.proto b/proto/kava/kavamint/v1beta1/kavamint.proto new file mode 100644 index 00000000..98fb10a6 --- /dev/null +++ b/proto/kava/kavamint/v1beta1/kavamint.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; +package kava.kavamint.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; + +option go_package = "github.com/kava-labs/kava/x/kavamint/types"; + +// Params wraps the governance parameters for the kavamint module +message Params { + option (gogoproto.goproto_stringer) = false; + option (gogoproto.goproto_getters) = false; + + // yearly inflation of total token supply minted to the community pool. + string community_pool_inflation = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; + // yearly inflation of bonded tokens minted for staking rewards to validators. + string staking_rewards_apy = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/proto/kava/kavamint/v1beta1/query.proto b/proto/kava/kavamint/v1beta1/query.proto new file mode 100644 index 00000000..36aaa0fe --- /dev/null +++ b/proto/kava/kavamint/v1beta1/query.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; +package kava.kavamint.v1beta1; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "kava/kavamint/v1beta1/kavamint.proto"; + +option go_package = "github.com/kava-labs/kava/x/kavamint/types"; + +// Query defines the gRPC querier service for kavamint. +service Query { + // Params queries the parameters of x/kavamint module. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/kava/kavamint/v1beta1/params"; + } + + // Inflation queries x/kavamint for the overall cumulative inflation rate of KAVA. + rpc Inflation(QueryInflationRequest) returns (QueryInflationResponse) { + option (google.api.http).get = "/kava/kavamint/v1beta1/inflation"; + } +} + +// QueryParamsRequest defines the request type for querying x/kavamint parameters. +message QueryParamsRequest {} + +// QueryParamsResponse defines the response type for querying x/kavamint parameters. +message QueryParamsResponse { + Params params = 1 [(gogoproto.nullable) = false]; +} + +// QueryInflationRequest is the request type for the Query/Inflation RPC method. +message QueryInflationRequest {} + +// QueryInflationResponse is the response type for the Query/Inflation RPC method. +message QueryInflationResponse { + // inflation is the current minting inflation value. + // example "0.990000000000000000" - 99% inflation + string inflation = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/x/community/abci.go b/x/community/abci.go new file mode 100644 index 00000000..1a4c88c0 --- /dev/null +++ b/x/community/abci.go @@ -0,0 +1,12 @@ +package community + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/community/keeper" + abci "github.com/tendermint/tendermint/abci/types" +) + +// BeginBlocker runs at the start of every block. +func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k keeper.Keeper) { + // TODO: process proposals here +} diff --git a/x/community/client/cli/query.go b/x/community/client/cli/query.go new file mode 100644 index 00000000..e5336147 --- /dev/null +++ b/x/community/client/cli/query.go @@ -0,0 +1,55 @@ +package cli + +import ( + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/kava-labs/kava/x/community/types" +) + +// GetQueryCmd returns the cli query commands for the community module. +func GetQueryCmd() *cobra.Command { + communityQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the community module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + commands := []*cobra.Command{ + GetCmdQueryBalance(), + } + + for _, cmd := range commands { + flags.AddQueryFlagsToCmd(cmd) + } + + communityQueryCmd.AddCommand(commands...) + + return communityQueryCmd +} + +// GetCmdQueryBalance implements a command to return the current community pool balance. +func GetCmdQueryBalance() *cobra.Command { + return &cobra.Command{ + Use: "balance", + Short: "Query the current balance of the community module account", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + res, err := queryClient.Balance(cmd.Context(), &types.QueryBalanceRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } +} diff --git a/x/community/client/cli/tx.go b/x/community/client/cli/tx.go new file mode 100644 index 00000000..bcf103bb --- /dev/null +++ b/x/community/client/cli/tx.go @@ -0,0 +1,66 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + + "github.com/kava-labs/kava/x/community/types" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + communityTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "community module transactions subcommands", + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmds := []*cobra.Command{ + getCmdFundCommunityPool(), + } + + for _, cmd := range cmds { + flags.AddTxFlagsToCmd(cmd) + } + + communityTxCmd.AddCommand(cmds...) + + return communityTxCmd +} + +func getCmdFundCommunityPool() *cobra.Command { + return &cobra.Command{ + Use: "fund-community-pool [coins]", + Short: "funds the community pool", + Long: "Fund community pool removes the listed coins from the sender's account and send them to the community module account.", + Args: cobra.ExactArgs(1), + Example: fmt.Sprintf( + `%s tx %s fund-community-module 10000000ukava --from `, version.AppName, types.ModuleName, + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + coins, err := sdk.ParseCoinsNormalized(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgFundCommunityPool(clientCtx.GetFromAddress(), coins) + if err := msg.ValidateBasic(); err != nil { + return err + } + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } +} diff --git a/x/community/genesis.go b/x/community/genesis.go new file mode 100644 index 00000000..4a187e59 --- /dev/null +++ b/x/community/genesis.go @@ -0,0 +1,19 @@ +// the community module has no genesis state but must init its module account on init +package community + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/types" +) + +// InitGenesis initializes the community module account +func InitGenesis(ctx sdk.Context, k keeper.Keeper, ak types.AccountKeeper) { + // check if the module account exists + if moduleAcc := ak.GetModuleAccount(ctx, types.ModuleAccountName); moduleAcc == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleAccountName)) + } +} diff --git a/x/community/genesis_test.go b/x/community/genesis_test.go new file mode 100644 index 00000000..758a8cba --- /dev/null +++ b/x/community/genesis_test.go @@ -0,0 +1,44 @@ +package community_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/kava-labs/kava/x/community" + "github.com/kava-labs/kava/x/community/testutil" + "github.com/kava-labs/kava/x/community/types" +) + +type genesisTestSuite struct { + testutil.Suite +} + +func (suite *genesisTestSuite) SetupTest() { + suite.Suite.SetupTest() +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(genesisTestSuite)) +} + +func (suite *genesisTestSuite) TestInitGenesis() { + suite.SetupTest() + + accountKeeper := suite.App.GetAccountKeeper() + + suite.NotPanics(func() { + community.InitGenesis(suite.Ctx, suite.Keeper, accountKeeper) + }) + + communityPoolAddress := accountKeeper.GetModuleAddress(types.ModuleAccountName) + // hello, greppers! + suite.Equal("kava17d2wax0zhjrrecvaszuyxdf5wcu5a0p4qlx3t5", communityPoolAddress.String()) + + // check for module account this way b/c GetModuleAccount creates if not existing. + acc := accountKeeper.GetAccount(suite.Ctx, communityPoolAddress) + suite.NotNil(acc) + _, ok := acc.(authtypes.ModuleAccountI) + suite.True(ok) +} diff --git a/x/community/keeper/grpc_query.go b/x/community/keeper/grpc_query.go new file mode 100644 index 00000000..160dcd75 --- /dev/null +++ b/x/community/keeper/grpc_query.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/community/types" +) + +type queryServer struct { + keeper Keeper +} + +var _ types.QueryServer = queryServer{} + +// NewQueryServerImpl creates a new server for handling gRPC queries. +func NewQueryServerImpl(k Keeper) types.QueryServer { + return &queryServer{keeper: k} +} + +func (s queryServer) Balance(c context.Context, _ *types.QueryBalanceRequest) (*types.QueryBalanceResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + return &types.QueryBalanceResponse{ + Coins: s.keeper.GetModuleAccountBalance(ctx), + }, nil +} diff --git a/x/community/keeper/grpc_query_test.go b/x/community/keeper/grpc_query_test.go new file mode 100644 index 00000000..7daaf983 --- /dev/null +++ b/x/community/keeper/grpc_query_test.go @@ -0,0 +1,66 @@ +package keeper_test + +import ( + "context" + "testing" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/types" +) + +type grpcQueryTestSuite struct { + KeeperTestSuite + + queryClient types.QueryClient +} + +func (suite *grpcQueryTestSuite) SetupTest() { + suite.KeeperTestSuite.SetupTest() + + queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, keeper.NewQueryServerImpl(suite.Keeper)) + + suite.queryClient = types.NewQueryClient(queryHelper) +} + +func TestGrpcQueryTestSuite(t *testing.T) { + suite.Run(t, new(grpcQueryTestSuite)) +} + +func (suite *grpcQueryTestSuite) TestGrpcQueryBalance() { + var expCoins sdk.Coins + + testCases := []struct { + name string + setup func() + }{ + { + name: "handles response with no balance", + setup: func() { expCoins = sdk.Coins{} }, + }, + { + name: "handles response with balance", + setup: func() { + expCoins = sdk.NewCoins( + sdk.NewCoin("ukava", sdk.NewInt(100)), + sdk.NewCoin("usdx", sdk.NewInt(1000)), + ) + suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, expCoins) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + tc.setup() + res, err := suite.queryClient.Balance(context.Background(), &types.QueryBalanceRequest{}) + suite.Require().NoError(err) + suite.Require().True(expCoins.IsEqual(res.Coins)) + }) + } +} diff --git a/x/community/keeper/keeper.go b/x/community/keeper/keeper.go new file mode 100644 index 00000000..8293d840 --- /dev/null +++ b/x/community/keeper/keeper.go @@ -0,0 +1,50 @@ +package keeper + +import ( + "fmt" + + "github.com/tendermint/tendermint/libs/log" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/community/types" +) + +// Keeper of the community store +type Keeper struct { + bankKeeper types.BankKeeper + moduleAddress sdk.AccAddress +} + +// NewKeeper creates a new community Keeper instance +func NewKeeper(ak types.AccountKeeper, bk types.BankKeeper) Keeper { + // ensure community module account is set + addr := ak.GetModuleAddress(types.ModuleAccountName) + if addr == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleAccountName)) + } + + return Keeper{ + bankKeeper: bk, + moduleAddress: addr, + } +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+types.ModuleName) +} + +// GetModuleAccountBalance returns all the coins held by the community module account +func (k Keeper) GetModuleAccountBalance(ctx sdk.Context) sdk.Coins { + return k.bankKeeper.GetAllBalances(ctx, k.moduleAddress) +} + +// FundCommunityPool transfers coins from the sender to the community module account. +func (k Keeper) FundCommunityPool(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coins) error { + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleAccountName, amount) +} + +// DistributeFromCommunityPool transfers coins from the community pool to recipient. +func (k Keeper) DistributeFromCommunityPool(ctx sdk.Context, recipient sdk.AccAddress, amount sdk.Coins) error { + return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleAccountName, recipient, amount) +} diff --git a/x/community/keeper/keeper_test.go b/x/community/keeper/keeper_test.go new file mode 100644 index 00000000..b998e021 --- /dev/null +++ b/x/community/keeper/keeper_test.go @@ -0,0 +1,63 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/x/community/testutil" + "github.com/kava-labs/kava/x/community/types" +) + +// Test suite used for all keeper tests +type KeeperTestSuite struct { + testutil.Suite +} + +// The default state used by each test +func (suite *KeeperTestSuite) SetupTest() { + suite.Suite.SetupTest() +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestCommunityPool() { + suite.SetupTest() + maccAddr := suite.App.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName) + + funds := sdk.NewCoins( + sdk.NewCoin("ukava", sdk.NewInt(10000)), + sdk.NewCoin("usdx", sdk.NewInt(100)), + ) + sender := suite.CreateFundedAccount(funds) + + suite.Run("FundCommunityPool", func() { + err := suite.Keeper.FundCommunityPool(suite.Ctx, sender, funds) + suite.Require().NoError(err) + + // check that community pool received balance + suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, funds) + // check that sender had balance deducted + suite.App.CheckBalance(suite.T(), suite.Ctx, sender, sdk.NewCoins()) + }) + + // send it back + suite.Run("DistributeFromCommunityPool - valid", func() { + err := suite.Keeper.DistributeFromCommunityPool(suite.Ctx, sender, funds) + suite.Require().NoError(err) + + // community pool has funds deducted + suite.App.CheckBalance(suite.T(), suite.Ctx, maccAddr, sdk.NewCoins()) + // receiver receives the funds + suite.App.CheckBalance(suite.T(), suite.Ctx, sender, funds) + }) + + // can't send more than we have! + suite.Run("DistributeFromCommunityPool - insufficient funds", func() { + err := suite.Keeper.DistributeFromCommunityPool(suite.Ctx, sender, funds) + suite.Require().ErrorContains(err, "insufficient funds") + }) +} diff --git a/x/community/keeper/msg_server.go b/x/community/keeper/msg_server.go new file mode 100644 index 00000000..66e639ec --- /dev/null +++ b/x/community/keeper/msg_server.go @@ -0,0 +1,49 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/community/types" +) + +type msgServer struct { + keeper Keeper +} + +// NewMsgServerImpl returns an implementation of the community MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(keeper Keeper) types.MsgServer { + return &msgServer{keeper: keeper} +} + +var _ types.MsgServer = msgServer{} + +// FundCommunityPool handles FundCommunityPool msgs. +func (s msgServer) FundCommunityPool(goCtx context.Context, msg *types.MsgFundCommunityPool) (*types.MsgFundCommunityPoolResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + + // above validation will fail if depositor is invalid + depositor := sdk.MustAccAddressFromBech32(msg.Depositor) + + if err := s.keeper.FundCommunityPool(ctx, depositor, msg.Amount); err != nil { + return nil, err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeyAction, types.AttributeValueFundCommunityPool), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Depositor), + sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()), + ), + ) + + return &types.MsgFundCommunityPoolResponse{}, nil +} diff --git a/x/community/keeper/msg_server_test.go b/x/community/keeper/msg_server_test.go new file mode 100644 index 00000000..b0e3e3b5 --- /dev/null +++ b/x/community/keeper/msg_server_test.go @@ -0,0 +1,111 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/testutil" + "github.com/kava-labs/kava/x/community/types" +) + +type msgServerTestSuite struct { + testutil.Suite + + communityPool sdk.AccAddress + msgServer types.MsgServer +} + +func (suite *msgServerTestSuite) SetupTest() { + suite.Suite.SetupTest() + + suite.communityPool = suite.App.GetAccountKeeper().GetModuleAddress(types.ModuleAccountName) + suite.msgServer = keeper.NewMsgServerImpl(suite.Keeper) +} + +func TestMsgServerTestSuite(t *testing.T) { + suite.Run(t, new(msgServerTestSuite)) +} + +func (suite *msgServerTestSuite) TestMsgFundCommunityPool() { + singleCoin := sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(2e6))) + multipleCoins := sdk.NewCoins( + sdk.NewCoin("ukava", sdk.NewInt(3e6)), + sdk.NewCoin("usdx", sdk.NewInt(1e7)), + ) + testCases := []struct { + name string + setup func() *types.MsgFundCommunityPool + expectedBalance sdk.Coins + shouldPass bool + }{ + { + name: "valid funding of single coin", + setup: func() *types.MsgFundCommunityPool { + sender := app.RandomAddress() + suite.App.FundAccount(suite.Ctx, sender, singleCoin) + return &types.MsgFundCommunityPool{ + Amount: singleCoin, + Depositor: sender.String(), + } + }, + expectedBalance: singleCoin, + shouldPass: true, + }, + { + name: "valid funding of multiple coins", + setup: func() *types.MsgFundCommunityPool { + sender := app.RandomAddress() + suite.App.FundAccount(suite.Ctx, sender, multipleCoins) + return &types.MsgFundCommunityPool{ + Amount: multipleCoins, + Depositor: sender.String(), + } + }, + expectedBalance: multipleCoins, + shouldPass: true, + }, + { + name: "invalid - failing message validation", + setup: func() *types.MsgFundCommunityPool { + return &types.MsgFundCommunityPool{ + Amount: sdk.NewCoins(), + Depositor: app.RandomAddress().String(), + } + }, + expectedBalance: sdk.NewCoins(), + shouldPass: false, + }, + { + name: "invalid - failing tx, insufficient funds", + setup: func() *types.MsgFundCommunityPool { + return &types.MsgFundCommunityPool{ + Amount: singleCoin, + Depositor: app.RandomAddress().String(), + } + }, + expectedBalance: sdk.NewCoins(), + shouldPass: false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + msg := tc.setup() + _, err := suite.msgServer.FundCommunityPool(sdk.WrapSDKContext(suite.Ctx), msg) + if tc.shouldPass { + suite.NoError(err) + } else { + suite.Error(err) + } + + balance := suite.Keeper.GetModuleAccountBalance(suite.Ctx) + suite.App.CheckBalance(suite.T(), suite.Ctx, suite.communityPool, balance) + }) + } +} diff --git a/x/community/module.go b/x/community/module.go new file mode 100644 index 00000000..7cc9fe25 --- /dev/null +++ b/x/community/module.go @@ -0,0 +1,146 @@ +package community + +import ( + "context" + "encoding/json" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/kava-labs/kava/x/community/client/cli" + "github.com/kava-labs/kava/x/community/keeper" + "github.com/kava-labs/kava/x/community/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic app module basics object +type AppModuleBasic struct{} + +// Name returns the module name +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec register module codec +// Deprecated: unused but necessary to fulfill AppModuleBasic interface +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// DefaultGenesis default genesis state +func (AppModuleBasic) DefaultGenesis(_ codec.JSONCodec) json.RawMessage { + return []byte("{}") +} + +// ValidateGenesis module validate genesis +func (AppModuleBasic) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConfig, _ json.RawMessage) error { + return nil +} + +// RegisterInterfaces implements InterfaceModule.RegisterInterfaces +func (a AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// RegisterRESTRoutes registers REST routes for the module. +// Deprecated: unused but necessary to fulfill AppModuleBasic interface +func (a AppModuleBasic) RegisterRESTRoutes(_ client.Context, _ *mux.Router) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. +func (a AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { + panic(err) + } +} + +// GetTxCmd returns the root tx command for the module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns no root query command for the module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +//____________________________________________________________________________ + +// AppModule app module type +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper keeper.Keeper, ak types.AccountKeeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + accountKeeper: ak, + } +} + +// Name module name +func (am AppModule) Name() string { + return am.AppModuleBasic.Name() +} + +// RegisterInvariants register module invariants +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route module message route name +// Deprecated: unused but necessary to fulfill AppModule interface +func (am AppModule) Route() sdk.Route { return sdk.Route{} } + +// QuerierRoute module querier route name +// Deprecated: unused but necessary to fulfill AppModule interface +func (AppModule) QuerierRoute() string { return "" } + +// LegacyQuerierHandler returns no sdk.Querier. +// Deprecated: unused but necessary to fulfill AppModule interface +func (am AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier { + return nil +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// RegisterServices registers module services. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), keeper.NewQueryServerImpl(am.keeper)) +} + +// InitGenesis module init-genesis +func (am AppModule) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, _ json.RawMessage) []abci.ValidatorUpdate { + InitGenesis(ctx, am.keeper, am.accountKeeper) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis module export genesis +func (am AppModule) ExportGenesis(_ sdk.Context, cdc codec.JSONCodec) json.RawMessage { + return am.DefaultGenesis(cdc) +} + +// BeginBlock module begin-block +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock module end-block +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/community/module_test.go b/x/community/module_test.go new file mode 100644 index 00000000..3c73285a --- /dev/null +++ b/x/community/module_test.go @@ -0,0 +1,22 @@ +package community_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/community/types" +) + +func TestItCreatesModuleAccountOnInitBlock(t *testing.T) { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, tmproto.Header{Height: 1}) + tApp.InitializeFromGenesisStates() + + accKeeper := tApp.GetAccountKeeper() + acc := accKeeper.GetAccount(ctx, authtypes.NewModuleAddress(types.ModuleName)) + require.NotNil(t, acc) +} diff --git a/x/community/spec/01_concepts.md b/x/community/spec/01_concepts.md new file mode 100644 index 00000000..b615203e --- /dev/null +++ b/x/community/spec/01_concepts.md @@ -0,0 +1,19 @@ + + +# Concepts + +## Community Pool + +The community pool is the module account of the x/community module. It replaces the functionality of the community pool fee collector account of the auth module in the vanilla SDK. + +### Funding + +The community pool can be funded every block from the community pool inflation of the x/kavamint module. + +Additionally, the pool can be funded by any account sending a community/FundCommunityPool message. + +### Spending + +The community pool funds are spent via government proposals. The x/kavadist module includes a CommunityPoolMultiSpendProposal that, upon approval, distributes funds to a list of accounts. diff --git a/x/community/spec/02_state.md b/x/community/spec/02_state.md new file mode 100644 index 00000000..d47420ae --- /dev/null +++ b/x/community/spec/02_state.md @@ -0,0 +1,7 @@ + + +# State + +The x/community module has no state. diff --git a/x/community/spec/03_messages.md b/x/community/spec/03_messages.md new file mode 100644 index 00000000..8e332ee2 --- /dev/null +++ b/x/community/spec/03_messages.md @@ -0,0 +1,17 @@ + + +# Messages + +## FundCommunityPool + +This message sends coins directly from the sender to the community module account. + +The transaction fails if the amount cannot be transferred from the sender to the community module account. + +```go +func (k Keeper) FundCommunityPool(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coins) error { + return k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleAccountName, amount) +} +``` diff --git a/x/community/spec/04_events.md b/x/community/spec/04_events.md new file mode 100644 index 00000000..34674507 --- /dev/null +++ b/x/community/spec/04_events.md @@ -0,0 +1,18 @@ + + +# Events + +The community module emits the following events: + +## Handlers + +### MsgFundCommunityPool + +| Type | Attribute Key | Attribute Value | +| ------- | ------------- | ------------------- | +| message | module | community | +| message | action | fund_community_pool | +| message | sender | {senderAddress} | +| message | amount | {amountCoins} | diff --git a/x/community/spec/README.md b/x/community/spec/README.md new file mode 100644 index 00000000..f79a9301 --- /dev/null +++ b/x/community/spec/README.md @@ -0,0 +1,19 @@ + + +# `community` + + + +1. **[Concepts](01_concepts.md)** +2. **[State](02_state.md)** +3. **[Messages](03_messages.md)** +4. **[Events](04_events.md)** + +## Abstract + +`x/community` is an implementation of a Cosmos SDK Module that provides for functionality and governance for a community pool of funds controlled by Kava DAO. diff --git a/x/community/testutil/main.go b/x/community/testutil/main.go new file mode 100644 index 00000000..8469d8ed --- /dev/null +++ b/x/community/testutil/main.go @@ -0,0 +1,41 @@ +package testutil + +import ( + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/community/keeper" +) + +// Test suite used for all community tests +type Suite struct { + suite.Suite + App app.TestApp + Ctx sdk.Context + Keeper keeper.Keeper +} + +// The default state used by each test +func (suite *Suite) SetupTest() { + app.SetSDKConfig() + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) + + tApp.InitializeFromGenesisStates() + + suite.App = tApp + suite.Ctx = ctx + suite.Keeper = tApp.GetCommunityKeeper() +} + +// CreateFundedAccount creates a random account and mints `coins` to it. +func (suite *Suite) CreateFundedAccount(coins sdk.Coins) sdk.AccAddress { + addr := app.RandomAddress() + err := suite.App.FundAccount(suite.Ctx, addr, coins) + suite.Require().NoError(err) + return addr +} diff --git a/x/community/types/codec.go b/x/community/types/codec.go new file mode 100644 index 00000000..838c7779 --- /dev/null +++ b/x/community/types/codec.go @@ -0,0 +1,36 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" + + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// RegisterLegacyAminoCodec registers all the necessary types and interfaces for the module. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + cdc.RegisterConcrete(&MsgFundCommunityPool{}, "community/MsgFundCommunityPool", nil) +} + +// RegisterInterfaces registers proto messages under their interfaces for unmarshalling, +// in addition to registering the msg service for handling tx msgs. +func RegisterInterfaces(registry types.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgFundCommunityPool{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} + +var ( + amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewAminoCodec(amino) +) + +func init() { + RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) +} diff --git a/x/community/types/events.go b/x/community/types/events.go new file mode 100644 index 00000000..92852bce --- /dev/null +++ b/x/community/types/events.go @@ -0,0 +1,7 @@ +package types + +// Community module event types +const ( + AttributeValueFundCommunityPool = "fund_community_pool" + AttributeValueCategory = ModuleName +) diff --git a/x/community/types/expected_keepers.go b/x/community/types/expected_keepers.go new file mode 100644 index 00000000..5300772d --- /dev/null +++ b/x/community/types/expected_keepers.go @@ -0,0 +1,20 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// AccountKeeper defines the contract required for account APIs. +type AccountKeeper interface { + GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI + GetModuleAddress(name string) sdk.AccAddress +} + +// BankKeeper defines the contract needed to be fulfilled for banking dependencies. +type BankKeeper interface { + GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error +} diff --git a/x/community/types/keys.go b/x/community/types/keys.go new file mode 100644 index 00000000..9a0d7c8f --- /dev/null +++ b/x/community/types/keys.go @@ -0,0 +1,12 @@ +package types + +const ( + // module name + ModuleName = "community" + + // ModuleAccountName is the name of the module's account + ModuleAccountName = ModuleName + + // Query endpoints supported by community + QueryBalance = "balance" +) diff --git a/x/community/types/msg.go b/x/community/types/msg.go new file mode 100644 index 00000000..2deed288 --- /dev/null +++ b/x/community/types/msg.go @@ -0,0 +1,56 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx" +) + +// ensure Msg interface compliance at compile time +var ( + _ sdk.Msg = &MsgFundCommunityPool{} + _ legacytx.LegacyMsg = &MsgFundCommunityPool{} +) + +// NewMsgFundCommunityPool returns a new MsgFundCommunityPool +func NewMsgFundCommunityPool(depositor sdk.AccAddress, amount sdk.Coins) MsgFundCommunityPool { + return MsgFundCommunityPool{ + Depositor: depositor.String(), + Amount: amount, + } +} + +// Route return the message type used for routing the message. +func (msg MsgFundCommunityPool) Route() string { return ModuleName } + +// Type returns a human-readable string for the message, intended for utilization within tags. +func (msg MsgFundCommunityPool) Type() string { return sdk.MsgTypeURL(&msg) } + +// ValidateBasic does a simple validation check that doesn't require access to any other information. +func (msg MsgFundCommunityPool) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Depositor) + if err != nil { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error()) + } + + if msg.Amount.IsAnyNil() || !msg.Amount.IsValid() || msg.Amount.IsZero() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "'%s'", msg.Amount) + } + + return nil +} + +// GetSignBytes gets the canonical byte representation of the Msg. +func (msg MsgFundCommunityPool) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(&msg) + return sdk.MustSortJSON(bz) +} + +// GetSigners returns the addresses of signers that must sign. +func (msg MsgFundCommunityPool) GetSigners() []sdk.AccAddress { + depositor, err := sdk.AccAddressFromBech32(msg.Depositor) + if err != nil { + panic(err) + } + return []sdk.AccAddress{depositor} +} diff --git a/x/community/types/msg_test.go b/x/community/types/msg_test.go new file mode 100644 index 00000000..487713af --- /dev/null +++ b/x/community/types/msg_test.go @@ -0,0 +1,93 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/community/types" +) + +func TestFundCommunityPool_ValidateBasic(t *testing.T) { + validCoins := sdk.NewCoins( + sdk.NewCoin("ukava", sdk.NewIntFromUint64(1e6)), + sdk.NewCoin("some-denom", sdk.NewIntFromUint64(1e4)), + ) + testCases := []struct { + name string + shouldPass bool + message types.MsgFundCommunityPool + }{ + { + name: "valid message", + shouldPass: true, + message: types.NewMsgFundCommunityPool(app.RandomAddress(), validCoins), + }, + { + name: "invalid - bad depositor", + shouldPass: false, + message: types.MsgFundCommunityPool{ + Depositor: "not-an-address", + Amount: validCoins, + }, + }, + { + name: "invalid - empty coins", + shouldPass: false, + message: types.MsgFundCommunityPool{ + Depositor: app.RandomAddress().String(), + Amount: sdk.NewCoins(), + }, + }, + { + name: "invalid - nil coins", + shouldPass: false, + message: types.MsgFundCommunityPool{ + Depositor: app.RandomAddress().String(), + Amount: nil, + }, + }, + { + name: "invalid - zero coins", + shouldPass: false, + message: types.MsgFundCommunityPool{ + Depositor: app.RandomAddress().String(), + Amount: sdk.NewCoins( + sdk.NewCoin("ukava", sdk.ZeroInt()), + ), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.message.ValidateBasic() + if tc.shouldPass { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +func TestFundCommunityPool_GetSigners(t *testing.T) { + t.Run("valid", func(t *testing.T) { + address := app.RandomAddress() + signers := types.MsgFundCommunityPool{ + Depositor: address.String(), + }.GetSigners() + require.Len(t, signers, 1) + require.Equal(t, address, signers[0]) + }) + + t.Run("panics when depositor is invalid", func(t *testing.T) { + require.Panics(t, func() { + types.MsgFundCommunityPool{ + Depositor: "not-an-address", + }.GetSigners() + }) + }) +} diff --git a/x/community/types/query.pb.go b/x/community/types/query.pb.go new file mode 100644 index 00000000..fc97ec24 --- /dev/null +++ b/x/community/types/query.pb.go @@ -0,0 +1,550 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: kava/community/v1beta1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryBalanceRequest defines the request type for querying x/community balance. +type QueryBalanceRequest struct { +} + +func (m *QueryBalanceRequest) Reset() { *m = QueryBalanceRequest{} } +func (m *QueryBalanceRequest) String() string { return proto.CompactTextString(m) } +func (*QueryBalanceRequest) ProtoMessage() {} +func (*QueryBalanceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_f236f06c43149273, []int{0} +} +func (m *QueryBalanceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBalanceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBalanceRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBalanceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBalanceRequest.Merge(m, src) +} +func (m *QueryBalanceRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryBalanceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBalanceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBalanceRequest proto.InternalMessageInfo + +// QueryBalanceResponse defines the response type for querying x/community balance. +type QueryBalanceResponse struct { + Coins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=coins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"coins"` +} + +func (m *QueryBalanceResponse) Reset() { *m = QueryBalanceResponse{} } +func (m *QueryBalanceResponse) String() string { return proto.CompactTextString(m) } +func (*QueryBalanceResponse) ProtoMessage() {} +func (*QueryBalanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_f236f06c43149273, []int{1} +} +func (m *QueryBalanceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryBalanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryBalanceResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryBalanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryBalanceResponse.Merge(m, src) +} +func (m *QueryBalanceResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryBalanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryBalanceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryBalanceResponse proto.InternalMessageInfo + +func (m *QueryBalanceResponse) GetCoins() github_com_cosmos_cosmos_sdk_types.Coins { + if m != nil { + return m.Coins + } + return nil +} + +func init() { + proto.RegisterType((*QueryBalanceRequest)(nil), "kava.community.v1beta1.QueryBalanceRequest") + proto.RegisterType((*QueryBalanceResponse)(nil), "kava.community.v1beta1.QueryBalanceResponse") +} + +func init() { + proto.RegisterFile("kava/community/v1beta1/query.proto", fileDescriptor_f236f06c43149273) +} + +var fileDescriptor_f236f06c43149273 = []byte{ + // 325 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x51, 0x4f, 0x4a, 0xf3, 0x40, + 0x14, 0x4f, 0xbe, 0x8f, 0x2a, 0xc4, 0x5d, 0xac, 0xa2, 0x45, 0xa6, 0x9a, 0x8d, 0x85, 0xda, 0x19, + 0x5b, 0x6f, 0x50, 0xf1, 0x00, 0x76, 0xe9, 0x6e, 0x26, 0x0e, 0x71, 0x68, 0x3b, 0x2f, 0xed, 0x9b, + 0x14, 0xb3, 0x75, 0x2f, 0x08, 0x2e, 0xbc, 0x83, 0x27, 0xe9, 0xb2, 0xe0, 0xc6, 0x95, 0x4a, 0xeb, + 0x41, 0x64, 0x32, 0xb1, 0x54, 0xa8, 0xe0, 0x2a, 0x8f, 0x97, 0xdf, 0xdf, 0x37, 0x41, 0xd4, 0xe7, + 0x13, 0xce, 0x62, 0x18, 0x0e, 0x33, 0xad, 0x4c, 0xce, 0x26, 0x6d, 0x21, 0x0d, 0x6f, 0xb3, 0x51, + 0x26, 0xc7, 0x39, 0x4d, 0xc7, 0x60, 0x20, 0xdc, 0xb5, 0x18, 0xba, 0xc4, 0xd0, 0x12, 0x53, 0x23, + 0x31, 0xe0, 0x10, 0x90, 0x09, 0x8e, 0x72, 0x49, 0x8c, 0x41, 0x69, 0xc7, 0xab, 0x55, 0x13, 0x48, + 0xa0, 0x18, 0x99, 0x9d, 0xca, 0xed, 0x41, 0x02, 0x90, 0x0c, 0x24, 0xe3, 0xa9, 0x62, 0x5c, 0x6b, + 0x30, 0xdc, 0x28, 0xd0, 0xe8, 0xfe, 0x46, 0x3b, 0xc1, 0xf6, 0xa5, 0xb5, 0xee, 0xf2, 0x01, 0xd7, + 0xb1, 0xec, 0xc9, 0x51, 0x26, 0xd1, 0x44, 0x79, 0x50, 0xfd, 0xb9, 0xc6, 0x14, 0x34, 0xca, 0x90, + 0x07, 0x15, 0x6b, 0x88, 0x7b, 0xfe, 0xe1, 0xff, 0xc6, 0x56, 0x67, 0x9f, 0xba, 0x48, 0xd4, 0x46, + 0xfa, 0xce, 0x49, 0xcf, 0x41, 0xe9, 0xee, 0xe9, 0xf4, 0xad, 0xee, 0x3d, 0xbf, 0xd7, 0x1b, 0x89, + 0x32, 0x37, 0x99, 0xb0, 0x75, 0x58, 0x99, 0xdf, 0x7d, 0x5a, 0x78, 0xdd, 0x67, 0x26, 0x4f, 0x25, + 0x16, 0x04, 0xec, 0x39, 0xe5, 0xce, 0x93, 0x1f, 0x54, 0x0a, 0xef, 0xf0, 0xde, 0x0f, 0x36, 0xcb, + 0x00, 0x61, 0x93, 0xae, 0x3f, 0x0a, 0x5d, 0x93, 0xbe, 0x76, 0xf2, 0x37, 0xb0, 0xeb, 0x14, 0x1d, + 0xdf, 0xbd, 0x7c, 0x3e, 0xfe, 0x3b, 0x0a, 0xeb, 0xec, 0x97, 0xb7, 0x11, 0x8e, 0xd0, 0xbd, 0x98, + 0xce, 0x89, 0x3f, 0x9b, 0x13, 0xff, 0x63, 0x4e, 0xfc, 0x87, 0x05, 0xf1, 0x66, 0x0b, 0xe2, 0xbd, + 0x2e, 0x88, 0x77, 0xd5, 0x5c, 0x29, 0x69, 0x45, 0x5a, 0x03, 0x2e, 0xd0, 0xc9, 0xdd, 0xae, 0x08, + 0x16, 0x6d, 0xc5, 0x46, 0x71, 0xf9, 0xb3, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x8f, 0x7f, + 0x0d, 0x0b, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Balance queries the balance of all coins of x/community module. + Balance(ctx context.Context, in *QueryBalanceRequest, opts ...grpc.CallOption) (*QueryBalanceResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Balance(ctx context.Context, in *QueryBalanceRequest, opts ...grpc.CallOption) (*QueryBalanceResponse, error) { + out := new(QueryBalanceResponse) + err := c.cc.Invoke(ctx, "/kava.community.v1beta1.Query/Balance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Balance queries the balance of all coins of x/community module. + Balance(context.Context, *QueryBalanceRequest) (*QueryBalanceResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Balance(ctx context.Context, req *QueryBalanceRequest) (*QueryBalanceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Balance not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Balance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryBalanceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Balance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kava.community.v1beta1.Query/Balance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Balance(ctx, req.(*QueryBalanceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "kava.community.v1beta1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Balance", + Handler: _Query_Balance_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kava/community/v1beta1/query.proto", +} + +func (m *QueryBalanceRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBalanceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBalanceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryBalanceResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryBalanceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryBalanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Coins) > 0 { + for iNdEx := len(m.Coins) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Coins[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryBalanceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryBalanceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Coins) > 0 { + for _, e := range m.Coins { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryBalanceRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBalanceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBalanceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryBalanceResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryBalanceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryBalanceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Coins", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Coins = append(m.Coins, types.Coin{}) + if err := m.Coins[len(m.Coins)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/community/types/query.pb.gw.go b/x/community/types/query.pb.gw.go new file mode 100644 index 00000000..68b2777c --- /dev/null +++ b/x/community/types/query.pb.gw.go @@ -0,0 +1,153 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: kava/community/v1beta1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Balance_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBalanceRequest + var metadata runtime.ServerMetadata + + msg, err := client.Balance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Balance_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryBalanceRequest + var metadata runtime.ServerMetadata + + msg, err := server.Balance(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Balance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Balance_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Balance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Balance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Balance_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Balance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Balance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "community", "v1beta1", "balance"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Balance_0 = runtime.ForwardResponseMessage +) diff --git a/x/community/types/tx.pb.go b/x/community/types/tx.pb.go new file mode 100644 index 00000000..7cccf175 --- /dev/null +++ b/x/community/types/tx.pb.go @@ -0,0 +1,640 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: kava/community/v1beta1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgFundCommunityPool allows an account to directly fund the community module account. +type MsgFundCommunityPool struct { + Amount github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=amount,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount"` + Depositor string `protobuf:"bytes,2,opt,name=depositor,proto3" json:"depositor,omitempty"` +} + +func (m *MsgFundCommunityPool) Reset() { *m = MsgFundCommunityPool{} } +func (m *MsgFundCommunityPool) String() string { return proto.CompactTextString(m) } +func (*MsgFundCommunityPool) ProtoMessage() {} +func (*MsgFundCommunityPool) Descriptor() ([]byte, []int) { + return fileDescriptor_e81067e0fbdaca18, []int{0} +} +func (m *MsgFundCommunityPool) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgFundCommunityPool) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgFundCommunityPool.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgFundCommunityPool) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgFundCommunityPool.Merge(m, src) +} +func (m *MsgFundCommunityPool) XXX_Size() int { + return m.Size() +} +func (m *MsgFundCommunityPool) XXX_DiscardUnknown() { + xxx_messageInfo_MsgFundCommunityPool.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgFundCommunityPool proto.InternalMessageInfo + +// MsgFundCommunityPoolResponse defines the Msg/FundCommunityPool response type. +type MsgFundCommunityPoolResponse struct { +} + +func (m *MsgFundCommunityPoolResponse) Reset() { *m = MsgFundCommunityPoolResponse{} } +func (m *MsgFundCommunityPoolResponse) String() string { return proto.CompactTextString(m) } +func (*MsgFundCommunityPoolResponse) ProtoMessage() {} +func (*MsgFundCommunityPoolResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e81067e0fbdaca18, []int{1} +} +func (m *MsgFundCommunityPoolResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgFundCommunityPoolResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgFundCommunityPoolResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgFundCommunityPoolResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgFundCommunityPoolResponse.Merge(m, src) +} +func (m *MsgFundCommunityPoolResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgFundCommunityPoolResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgFundCommunityPoolResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgFundCommunityPoolResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgFundCommunityPool)(nil), "kava.community.v1beta1.MsgFundCommunityPool") + proto.RegisterType((*MsgFundCommunityPoolResponse)(nil), "kava.community.v1beta1.MsgFundCommunityPoolResponse") +} + +func init() { proto.RegisterFile("kava/community/v1beta1/tx.proto", fileDescriptor_e81067e0fbdaca18) } + +var fileDescriptor_e81067e0fbdaca18 = []byte{ + // 348 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcf, 0x4e, 0x2c, 0x4b, + 0xd4, 0x4f, 0xce, 0xcf, 0xcd, 0x2d, 0xcd, 0xcb, 0x2c, 0xa9, 0xd4, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, + 0x49, 0x34, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x03, 0x29, 0xd0, + 0x83, 0x2b, 0xd0, 0x83, 0x2a, 0x90, 0x92, 0x4b, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0xd6, 0x4f, 0x4a, + 0x2c, 0x4e, 0x85, 0xeb, 0x4a, 0xce, 0xcf, 0xcc, 0x83, 0xe8, 0x93, 0x92, 0x84, 0xc8, 0xc7, 0x83, + 0x79, 0xfa, 0x10, 0x0e, 0x54, 0x4a, 0x24, 0x3d, 0x3f, 0x3d, 0x1f, 0x22, 0x0e, 0x62, 0x41, 0x44, + 0x95, 0x76, 0x32, 0x72, 0x89, 0xf8, 0x16, 0xa7, 0xbb, 0x95, 0xe6, 0xa5, 0x38, 0xc3, 0x6c, 0x0b, + 0xc8, 0xcf, 0xcf, 0x11, 0x4a, 0xe6, 0x62, 0x4b, 0xcc, 0xcd, 0x2f, 0xcd, 0x2b, 0x91, 0x60, 0x54, + 0x60, 0xd6, 0xe0, 0x36, 0x92, 0xd4, 0x83, 0x9a, 0x06, 0xb2, 0x1a, 0xe6, 0x1e, 0x3d, 0xe7, 0xfc, + 0xcc, 0x3c, 0x27, 0x83, 0x13, 0xf7, 0xe4, 0x19, 0x56, 0xdd, 0x97, 0xd7, 0x48, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0x02, 0x39, 0x1b, 0x6a, 0x35, 0x94, 0xd2, 0x2d, 0x4e, 0xc9, 0xd6, 0x2f, 0xa9, 0x2c, + 0x48, 0x2d, 0x06, 0x6b, 0x28, 0x0e, 0x82, 0x1a, 0x2d, 0x64, 0xc6, 0xc5, 0x99, 0x92, 0x5a, 0x90, + 0x5f, 0x9c, 0x59, 0x92, 0x5f, 0x24, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0xe9, 0x24, 0x71, 0x69, 0x8b, + 0xae, 0x08, 0xd4, 0x2a, 0xc7, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0xe2, 0xe0, 0x92, 0xa2, 0xcc, 0xbc, + 0xf4, 0x20, 0x84, 0x52, 0x2b, 0x96, 0x8e, 0x05, 0xf2, 0x0c, 0x4a, 0x72, 0x5c, 0x32, 0xd8, 0x9c, + 0x1e, 0x94, 0x5a, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0x6a, 0x54, 0xc7, 0xc5, 0xec, 0x5b, 0x9c, 0x2e, + 0x54, 0xce, 0x25, 0x88, 0xe9, 0x3d, 0x1d, 0x3d, 0xec, 0x21, 0xac, 0x87, 0xcd, 0x44, 0x29, 0x13, + 0x52, 0x54, 0xc3, 0xec, 0x77, 0xf2, 0x5c, 0xf1, 0x48, 0x8e, 0xf1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, + 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, + 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xb4, 0x91, 0x42, 0x0b, 0x64, 0xba, 0x6e, 0x4e, 0x62, 0x52, 0x31, + 0x98, 0xa5, 0x5f, 0x81, 0x94, 0x34, 0xc0, 0xc1, 0x96, 0xc4, 0x06, 0x8e, 0x2d, 0x63, 0x40, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xc2, 0xe0, 0xfc, 0x03, 0x39, 0x02, 0x00, 0x00, +} + +func (this *MsgFundCommunityPool) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*MsgFundCommunityPool) + if !ok { + that2, ok := that.(MsgFundCommunityPool) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.Amount) != len(that1.Amount) { + return false + } + for i := range this.Amount { + if !this.Amount[i].Equal(&that1.Amount[i]) { + return false + } + } + if this.Depositor != that1.Depositor { + return false + } + return true +} +func (this *MsgFundCommunityPoolResponse) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*MsgFundCommunityPoolResponse) + if !ok { + that2, ok := that.(MsgFundCommunityPoolResponse) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + return true +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // FundCommunityPool defines a method to allow an account to directly fund the community module account. + FundCommunityPool(ctx context.Context, in *MsgFundCommunityPool, opts ...grpc.CallOption) (*MsgFundCommunityPoolResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) FundCommunityPool(ctx context.Context, in *MsgFundCommunityPool, opts ...grpc.CallOption) (*MsgFundCommunityPoolResponse, error) { + out := new(MsgFundCommunityPoolResponse) + err := c.cc.Invoke(ctx, "/kava.community.v1beta1.Msg/FundCommunityPool", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // FundCommunityPool defines a method to allow an account to directly fund the community module account. + FundCommunityPool(context.Context, *MsgFundCommunityPool) (*MsgFundCommunityPoolResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) FundCommunityPool(ctx context.Context, req *MsgFundCommunityPool) (*MsgFundCommunityPoolResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method FundCommunityPool not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_FundCommunityPool_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgFundCommunityPool) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).FundCommunityPool(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kava.community.v1beta1.Msg/FundCommunityPool", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).FundCommunityPool(ctx, req.(*MsgFundCommunityPool)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "kava.community.v1beta1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "FundCommunityPool", + Handler: _Msg_FundCommunityPool_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kava/community/v1beta1/tx.proto", +} + +func (m *MsgFundCommunityPool) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgFundCommunityPool) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgFundCommunityPool) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Depositor) > 0 { + i -= len(m.Depositor) + copy(dAtA[i:], m.Depositor) + i = encodeVarintTx(dAtA, i, uint64(len(m.Depositor))) + i-- + dAtA[i] = 0x12 + } + if len(m.Amount) > 0 { + for iNdEx := len(m.Amount) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Amount[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *MsgFundCommunityPoolResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgFundCommunityPoolResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgFundCommunityPoolResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgFundCommunityPool) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Amount) > 0 { + for _, e := range m.Amount { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + l = len(m.Depositor) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgFundCommunityPoolResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgFundCommunityPool) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgFundCommunityPool: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgFundCommunityPool: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Amount = append(m.Amount, types.Coin{}) + if err := m.Amount[len(m.Amount)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Depositor", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Depositor = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgFundCommunityPoolResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgFundCommunityPoolResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgFundCommunityPoolResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/earn/keeper/keeper.go b/x/earn/keeper/keeper.go index b9ffa3f0..582daed5 100644 --- a/x/earn/keeper/keeper.go +++ b/x/earn/keeper/keeper.go @@ -22,8 +22,8 @@ type Keeper struct { hardKeeper types.HardKeeper savingsKeeper types.SavingsKeeper - // Keeper for community pool transfers - distKeeper types.DistributionKeeper + // name of module account the community pool deposit/withdraw proposals use + communityPoolMaccName string } // NewKeeper creates a new keeper @@ -36,7 +36,7 @@ func NewKeeper( liquidKeeper types.LiquidKeeper, hardKeeper types.HardKeeper, savingsKeeper types.SavingsKeeper, - distKeeper types.DistributionKeeper, + communityPoolMaccName string, ) Keeper { if !paramstore.HasKeyTable() { paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) @@ -51,7 +51,8 @@ func NewKeeper( liquidKeeper: liquidKeeper, hardKeeper: hardKeeper, savingsKeeper: savingsKeeper, - distKeeper: distKeeper, + + communityPoolMaccName: communityPoolMaccName, } } diff --git a/x/earn/keeper/proposal_handler.go b/x/earn/keeper/proposal_handler.go index 03b08c7c..f003b435 100644 --- a/x/earn/keeper/proposal_handler.go +++ b/x/earn/keeper/proposal_handler.go @@ -9,8 +9,13 @@ import ( // HandleCommunityPoolDepositProposal is a handler for executing a passed community pool deposit proposal func HandleCommunityPoolDepositProposal(ctx sdk.Context, k Keeper, p *types.CommunityPoolDepositProposal) error { - fundAcc := k.accountKeeper.GetModuleAccount(ctx, kavadisttypes.FundModuleAccount) - if err := k.distKeeper.DistributeFromFeePool(ctx, sdk.NewCoins(p.Amount), fundAcc.GetAddress()); err != nil { + // move funds from community pool to the funding account + if err := k.bankKeeper.SendCoinsFromModuleToModule( + ctx, + k.communityPoolMaccName, + kavadisttypes.FundModuleAccount, + sdk.NewCoins(p.Amount), + ); err != nil { return err } @@ -32,18 +37,13 @@ func HandleCommunityPoolWithdrawProposal(ctx sdk.Context, k Keeper, p *types.Com } // Move funds to the community pool manually - err = k.bankKeeper.SendCoinsFromModuleToModule( + if err := k.bankKeeper.SendCoinsFromModuleToModule( ctx, kavadisttypes.FundModuleAccount, - k.distKeeper.GetDistributionAccount(ctx).GetName(), + k.communityPoolMaccName, sdk.NewCoins(withdrawAmount), - ) - if err != nil { + ); err != nil { return err } - feePool := k.distKeeper.GetFeePool(ctx) - newCommunityPool := feePool.CommunityPool.Add(sdk.NewDecCoinFromCoin(withdrawAmount)) - feePool.CommunityPool = newCommunityPool - k.distKeeper.SetFeePool(ctx, feePool) return nil } diff --git a/x/earn/keeper/proposal_handler_test.go b/x/earn/keeper/proposal_handler_test.go index 23856736..ce82969c 100644 --- a/x/earn/keeper/proposal_handler_test.go +++ b/x/earn/keeper/proposal_handler_test.go @@ -4,6 +4,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + communitytypes "github.com/kava-labs/kava/x/community/types" "github.com/kava-labs/kava/x/earn/keeper" "github.com/kava-labs/kava/x/earn/testutil" "github.com/kava-labs/kava/x/earn/types" @@ -24,15 +25,12 @@ func TestProposalTestSuite(t *testing.T) { } func (suite *proposalTestSuite) TestCommunityDepositProposal() { - distKeeper := suite.App.GetDistrKeeper() ctx := suite.Ctx - macc := distKeeper.GetDistributionAccount(ctx) + macc := suite.App.GetAccountKeeper().GetModuleAccount(ctx, communitytypes.ModuleAccountName) fundAmount := sdk.NewCoins(sdk.NewInt64Coin("ukava", 100000000)) depositAmount := sdk.NewCoin("ukava", sdk.NewInt(10000000)) suite.Require().NoError(suite.App.FundModuleAccount(ctx, macc.GetName(), fundAmount)) - feePool := distKeeper.GetFeePool(ctx) - feePool.CommunityPool = sdk.NewDecCoinsFromCoins(fundAmount...) - distKeeper.SetFeePool(ctx, feePool) + suite.CreateVault("ukava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil) prop := types.NewCommunityPoolDepositProposal("test title", "desc", depositAmount) @@ -41,22 +39,18 @@ func (suite *proposalTestSuite) TestCommunityDepositProposal() { balance := suite.BankKeeper.GetAllBalances(ctx, macc.GetAddress()) suite.Require().Equal(fundAmount.Sub(sdk.NewCoins(depositAmount)), balance) - feePool = distKeeper.GetFeePool(ctx) - communityPoolBalance, change := feePool.CommunityPool.TruncateDecimal() + + communityPoolBalance := suite.App.GetCommunityKeeper().GetModuleAccountBalance(ctx) suite.Require().Equal(fundAmount.Sub(sdk.NewCoins(depositAmount)), communityPoolBalance) - suite.Require().True(change.Empty()) } func (suite *proposalTestSuite) TestCommunityWithdrawProposal() { - distKeeper := suite.App.GetDistrKeeper() ctx := suite.Ctx - macc := distKeeper.GetDistributionAccount(ctx) + macc := suite.App.GetAccountKeeper().GetModuleAccount(ctx, communitytypes.ModuleAccountName) fundAmount := sdk.NewCoins(sdk.NewInt64Coin("ukava", 100000000)) depositAmount := sdk.NewCoin("ukava", sdk.NewInt(10000000)) suite.Require().NoError(suite.App.FundModuleAccount(ctx, macc.GetName(), fundAmount)) - feePool := distKeeper.GetFeePool(ctx) - feePool.CommunityPool = sdk.NewDecCoinsFromCoins(fundAmount...) - distKeeper.SetFeePool(ctx, feePool) + // TODO update to STRATEGY_TYPE_SAVINGS once implemented suite.CreateVault("ukava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil) deposit := types.NewCommunityPoolDepositProposal("test title", @@ -73,8 +67,7 @@ func (suite *proposalTestSuite) TestCommunityWithdrawProposal() { suite.Require().NoError(err) balance = suite.BankKeeper.GetAllBalances(ctx, macc.GetAddress()) suite.Require().Equal(fundAmount, balance) - feePool = distKeeper.GetFeePool(ctx) - communityPoolBalance, change := feePool.CommunityPool.TruncateDecimal() + + communityPoolBalance := suite.App.GetCommunityKeeper().GetModuleAccountBalance(ctx) suite.Require().Equal(fundAmount, communityPoolBalance) - suite.Require().True(change.Empty()) } diff --git a/x/earn/types/expected_keepers.go b/x/earn/types/expected_keepers.go index e2533c88..99dd28da 100644 --- a/x/earn/types/expected_keepers.go +++ b/x/earn/types/expected_keepers.go @@ -3,7 +3,6 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" - disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" hardtypes "github.com/kava-labs/kava/x/hard/types" savingstypes "github.com/kava-labs/kava/x/savings/types" @@ -26,14 +25,6 @@ type BankKeeper interface { SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } -// DistributionKeeper defines the expected interface needed for community-pool deposits to earn vaults -type DistributionKeeper interface { - GetFeePool(ctx sdk.Context) (feePool disttypes.FeePool) - SetFeePool(ctx sdk.Context, feePool disttypes.FeePool) - GetDistributionAccount(ctx sdk.Context) types.ModuleAccountI - DistributeFromFeePool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress) error -} - // LiquidKeeper defines the expected interface needed for derivative to staked token conversions. type LiquidKeeper interface { GetStakedTokensForDerivatives(ctx sdk.Context, derivatives sdk.Coins) (sdk.Coin, error) diff --git a/x/incentive/keeper/rewards_earn_staking_integration_test.go b/x/incentive/keeper/rewards_earn_staking_integration_test.go index f909bc42..a83b72eb 100644 --- a/x/incentive/keeper/rewards_earn_staking_integration_test.go +++ b/x/incentive/keeper/rewards_earn_staking_integration_test.go @@ -69,6 +69,8 @@ func (suite *EarnStakingRewardsIntegrationTestSuite) SetupTest() { WithMinter(sdk.OneDec(), sdk.ZeroDec()). WithMintDenom("ukava") + kavamintBuilder := testutil.NewKavamintGenesisBuilder() + suite.StartChainWithBuilders( authBuilder, incentiveBuilder, @@ -76,6 +78,7 @@ func (suite *EarnStakingRewardsIntegrationTestSuite) SetupTest() { earnBuilder, stakingBuilder, mintBuilder, + kavamintBuilder, ) } diff --git a/x/incentive/testutil/kavamint_builder.go b/x/incentive/testutil/kavamint_builder.go new file mode 100644 index 00000000..df21871a --- /dev/null +++ b/x/incentive/testutil/kavamint_builder.go @@ -0,0 +1,40 @@ +package testutil + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + + kavaminttypes "github.com/kava-labs/kava/x/kavamint/types" +) + +// KavamintGenesisBuilder is a tool for creating a mint genesis state. +// Helper methods add values onto a default genesis state. +// All methods are immutable and return updated copies of the builder. +type KavamintGenesisBuilder struct { + kavaminttypes.GenesisState +} + +var _ GenesisBuilder = (*KavamintGenesisBuilder)(nil) + +func NewKavamintGenesisBuilder() KavamintGenesisBuilder { + gen := kavaminttypes.DefaultGenesisState() + gen.Params.CommunityPoolInflation = sdk.ZeroDec() + gen.Params.StakingRewardsApy = sdk.ZeroDec() + + return KavamintGenesisBuilder{ + GenesisState: *gen, + } +} + +func (builder KavamintGenesisBuilder) Build() kavaminttypes.GenesisState { + return builder.GenesisState +} + +func (builder KavamintGenesisBuilder) BuildMarshalled(cdc codec.JSONCodec) app.GenesisState { + built := builder.Build() + + return app.GenesisState{ + kavaminttypes.ModuleName: cdc.MustMarshalJSON(&built), + } +} diff --git a/x/kavadist/keeper/keeper.go b/x/kavadist/keeper/keeper.go index 191a2156..90c10452 100644 --- a/x/kavadist/keeper/keeper.go +++ b/x/kavadist/keeper/keeper.go @@ -13,12 +13,12 @@ import ( // Keeper keeper for the cdp module type Keeper struct { - key sdk.StoreKey - cdc codec.BinaryCodec - paramSubspace paramtypes.Subspace - bankKeeper types.BankKeeper - distKeeper types.DistKeeper - accountKeeper types.AccountKeeper + key sdk.StoreKey + cdc codec.BinaryCodec + paramSubspace paramtypes.Subspace + bankKeeper types.BankKeeper + accountKeeper types.AccountKeeper + communityKeeper types.CommunityKeeper blacklistedAddrs map[string]bool } @@ -26,7 +26,7 @@ type Keeper struct { // NewKeeper creates a new keeper func NewKeeper( cdc codec.BinaryCodec, key sdk.StoreKey, paramstore paramtypes.Subspace, bk types.BankKeeper, ak types.AccountKeeper, - dk types.DistKeeper, blacklistedAddrs map[string]bool, + ck types.CommunityKeeper, blacklistedAddrs map[string]bool, ) Keeper { if !paramstore.HasKeyTable() { paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) @@ -37,8 +37,8 @@ func NewKeeper( cdc: cdc, paramSubspace: paramstore, bankKeeper: bk, - distKeeper: dk, accountKeeper: ak, + communityKeeper: ck, blacklistedAddrs: blacklistedAddrs, } } diff --git a/x/kavadist/keeper/proposal_handler.go b/x/kavadist/keeper/proposal_handler.go index 0cc47e2a..68540e42 100644 --- a/x/kavadist/keeper/proposal_handler.go +++ b/x/kavadist/keeper/proposal_handler.go @@ -13,7 +13,7 @@ func HandleCommunityPoolMultiSpendProposal(ctx sdk.Context, k Keeper, p *types.C if k.blacklistedAddrs[receiverInfo.Address] { return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is blacklisted from receiving external funds", receiverInfo.Address) } - err := k.distKeeper.DistributeFromFeePool(ctx, receiverInfo.Amount, receiverInfo.GetAddress()) + err := k.communityKeeper.DistributeFromCommunityPool(ctx, receiverInfo.GetAddress(), receiverInfo.Amount) if err != nil { return err } diff --git a/x/kavadist/keeper/proposal_handler_test.go b/x/kavadist/keeper/proposal_handler_test.go index 97857333..dc8ad5ea 100644 --- a/x/kavadist/keeper/proposal_handler_test.go +++ b/x/kavadist/keeper/proposal_handler_test.go @@ -3,21 +3,22 @@ package keeper_test import ( sdk "github.com/cosmos/cosmos-sdk/types" + communitytypes "github.com/kava-labs/kava/x/community/types" "github.com/kava-labs/kava/x/kavadist/keeper" "github.com/kava-labs/kava/x/kavadist/types" ) func (suite *keeperTestSuite) TestHandleCommunityPoolMultiSpendProposal() { - addr, distrKeeper, ctx := suite.Addrs[0], suite.App.GetDistrKeeper(), suite.Ctx + addr, communityKeeper, ctx := suite.Addrs[0], suite.App.GetCommunityKeeper(), suite.Ctx initBalances := suite.BankKeeper.GetAllBalances(ctx, addr) - // add coins to the module account and fund fee pool - macc := distrKeeper.GetDistributionAccount(ctx) - fundAmount := sdk.NewCoins(sdk.NewInt64Coin("ukava", 1000000)) - suite.Require().NoError(suite.App.FundModuleAccount(ctx, macc.GetName(), fundAmount)) - feePool := distrKeeper.GetFeePool(ctx) - feePool.CommunityPool = sdk.NewDecCoinsFromCoins(fundAmount...) - distrKeeper.SetFeePool(ctx, feePool) + // add coins to the module account and fund community pool + initialFunds := int64(1000000) + fundAmount := sdk.NewCoins(sdk.NewInt64Coin("ukava", initialFunds)) + suite.Require().NoError(suite.App.FundModuleAccount(ctx, communitytypes.ModuleAccountName, fundAmount)) + // expect funds to start in community pool + commPoolFunds := communityKeeper.GetModuleAccountBalance(ctx) + suite.Require().True(fundAmount.IsEqual(commPoolFunds)) proposalAmount1 := int64(1100) proposalAmount2 := int64(1200) @@ -35,6 +36,12 @@ func (suite *keeperTestSuite) TestHandleCommunityPoolMultiSpendProposal() { suite.Require().Nil(err) balances := suite.BankKeeper.GetAllBalances(ctx, addr) + + // expect funds to be transferred to recipient expected := initBalances.AmountOf("ukava").Add(sdk.NewInt(proposalAmount1 + proposalAmount2)) suite.Require().Equal(expected, balances.AmountOf("ukava")) + + // expect funds to be deducted from community pool + expectedCommPool := commPoolFunds.AmountOf("ukava").SubRaw(proposalAmount1 + proposalAmount2) + suite.Require().Equal(expectedCommPool, communityKeeper.GetModuleAccountBalance(ctx).AmountOf("ukava")) } diff --git a/x/kavadist/types/expected_keepers.go b/x/kavadist/types/expected_keepers.go index e78d3d9f..387a5423 100644 --- a/x/kavadist/types/expected_keepers.go +++ b/x/kavadist/types/expected_keepers.go @@ -5,9 +5,9 @@ import ( authTypes "github.com/cosmos/cosmos-sdk/x/auth/types" ) -// DistKeeper defines the expected distribution keeper interface -type DistKeeper interface { - DistributeFromFeePool(ctx sdk.Context, amount sdk.Coins, receiveAddr sdk.AccAddress) error +// CommunityKeeper defines the expected community keeper interface +type CommunityKeeper interface { + DistributeFromCommunityPool(ctx sdk.Context, sender sdk.AccAddress, amount sdk.Coins) error } // AccountKeeper defines the expected account keeper interface diff --git a/x/kavamint/abci.go b/x/kavamint/abci.go new file mode 100644 index 00000000..27b09592 --- /dev/null +++ b/x/kavamint/abci.go @@ -0,0 +1,75 @@ +package kavamint + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/kavamint/keeper" + "github.com/kava-labs/kava/x/kavamint/types" +) + +// BeginBlocker mints & distributes new tokens for the previous block. +func BeginBlocker(ctx sdk.Context, k keeper.KeeperI) { + params := k.GetParams(ctx) + // determine seconds since last mint + previousBlockTime := k.GetPreviousBlockTime(ctx) + if previousBlockTime.IsZero() { + previousBlockTime = ctx.BlockTime() + } + secondsPassed := ctx.BlockTime().Sub(previousBlockTime).Seconds() + + // calculate totals before any minting is done to prevent new mints affecting the values + totalSupply := k.TotalSupply(ctx) + totalBonded := k.TotalBondedTokens(ctx) + + // ------------- Staking Rewards ------------- + stakingRewardCoins, err := k.AccumulateInflation( + ctx, params.StakingRewardsApy, totalBonded, secondsPassed, + ) + if err != nil { + panic(err) + } + + // mint staking rewards + if err := k.MintCoins(ctx, stakingRewardCoins); err != nil { + panic(err) + } + + // send staking rewards to auth fee collector for distribution to validators + if err := k.AddCollectedFees(ctx, stakingRewardCoins); err != nil { + panic(err) + } + + // ------------- Community Pool ------------- + communityPoolInflation, err := k.AccumulateInflation( + ctx, params.CommunityPoolInflation, totalSupply, secondsPassed, + ) + if err != nil { + panic(err) + } + + // mint community pool inflation + if err := k.MintCoins(ctx, communityPoolInflation); err != nil { + panic(err) + } + + // send inflation coins to the community pool (x/community module account) + if err := k.FundCommunityPool(ctx, communityPoolInflation); err != nil { + panic(err) + } + + // ------------- Bookkeeping ------------- + // bookkeep the previous block time + k.SetPreviousBlockTime(ctx, ctx.BlockTime()) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeMint, + sdk.NewAttribute(types.AttributeKeyTotalSupply, totalSupply.String()), + sdk.NewAttribute(types.AttributeKeyTotalBonded, totalBonded.String()), + sdk.NewAttribute(types.AttributeSecondsPassed, fmt.Sprintf("%f", secondsPassed)), + sdk.NewAttribute(types.AttributeKeyCommunityPoolMint, communityPoolInflation.String()), + sdk.NewAttribute(types.AttributeKeyStakingRewardMint, stakingRewardCoins.String()), + ), + ) +} diff --git a/x/kavamint/abci_test.go b/x/kavamint/abci_test.go new file mode 100644 index 00000000..6dd4cfa2 --- /dev/null +++ b/x/kavamint/abci_test.go @@ -0,0 +1,196 @@ +package kavamint_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/stretchr/testify/suite" + + communitytypes "github.com/kava-labs/kava/x/community/types" + "github.com/kava-labs/kava/x/kavamint" + "github.com/kava-labs/kava/x/kavamint/keeper" + "github.com/kava-labs/kava/x/kavamint/testutil" + "github.com/kava-labs/kava/x/kavamint/types" +) + +type abciTestSuite struct { + testutil.KavamintTestSuite +} + +func (suite *abciTestSuite) SetupTest() { + suite.KavamintTestSuite.SetupTest() +} + +func (suite abciTestSuite) CheckModuleBalance(ctx sdk.Context, moduleName string, expectedAmount sdk.Int) { + denom := suite.StakingKeeper.BondDenom(ctx) + amount := suite.App.GetModuleAccountBalance(ctx, moduleName, denom) + suite.Require().Equal(expectedAmount, amount) +} + +func (suite *abciTestSuite) CheckFeeCollectorBalance(ctx sdk.Context, expectedAmount sdk.Int) { + suite.CheckModuleBalance(ctx, authtypes.FeeCollectorName, expectedAmount) +} + +func (suite *abciTestSuite) CheckKavamintBalance(ctx sdk.Context, expectedAmount sdk.Int) { + suite.CheckModuleBalance(ctx, types.ModuleName, expectedAmount) +} + +func (suite *abciTestSuite) CheckCommunityPoolBalance(ctx sdk.Context, expectedAmount sdk.Int) { + suite.CheckModuleBalance(ctx, communitytypes.ModuleAccountName, expectedAmount) +} + +func TestABCITestSuite(t *testing.T) { + suite.Run(t, new(abciTestSuite)) +} + +func (suite *abciTestSuite) Test_BeginBlocker_MintsExpectedTokens() { + testCases := []struct { + name string + blockTime uint64 + communityPoolInflation sdk.Dec + stakingRewardsApy sdk.Dec + bondedRatio sdk.Dec + setup func() + expCommunityPoolBalance sdk.Int + expFeeCollectorBalance sdk.Int + }{ + { + name: "sanity check: a year of seconds mints total yearly inflation", + blockTime: keeper.SecondsPerYear, + communityPoolInflation: sdk.NewDecWithPrec(20, 2), + stakingRewardsApy: sdk.NewDecWithPrec(20, 2), + bondedRatio: sdk.NewDecWithPrec(50, 2), + // 20% inflation on 1e10 tokens -> 2e9 minted + expCommunityPoolBalance: sdk.NewInt(2e9), + // 20% APY, 50% bonded (5e9) -> 1e9 minted + expFeeCollectorBalance: sdk.NewInt(1e9), + }, + { + name: "mints staking rewards, handles 0 community pool inflation", + blockTime: 6, + communityPoolInflation: sdk.ZeroDec(), + stakingRewardsApy: sdk.NewDecWithPrec(20, 2), + bondedRatio: sdk.NewDecWithPrec(40, 2), + expCommunityPoolBalance: sdk.ZeroInt(), + // 20% APY for 6 seconds + // bond ratio is 40%, so total supply * ratio = 1e10 * .4 + // https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.20%2C31536000%5D%5C%2841%29%2C6%5D-1%5C%2841%29*1e10*.4 + // => 138.75 => truncated to 138 tokens. + expFeeCollectorBalance: sdk.NewInt(138), + }, + { + name: "mints community pool inflation, handles 0 staking rewards", + blockTime: 6, + communityPoolInflation: sdk.NewDecWithPrec(80, 2), + stakingRewardsApy: sdk.ZeroDec(), + bondedRatio: sdk.NewDecWithPrec(40, 2), + // 80% APY for 6 seconds + // total supply = 1e10 + // https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.80%2C31536000%5D%5C%2841%29%2C6%5D-1%5C%2841%29*1e10 + // => 1118.32 => truncated to 1118 tokens. + expCommunityPoolBalance: sdk.NewInt(1118), + expFeeCollectorBalance: sdk.ZeroInt(), + }, + { + name: "mints no tokens if all inflation is zero", + blockTime: 6, + communityPoolInflation: sdk.ZeroDec(), + stakingRewardsApy: sdk.ZeroDec(), + bondedRatio: sdk.NewDecWithPrec(40, 2), + expCommunityPoolBalance: sdk.ZeroInt(), + expFeeCollectorBalance: sdk.ZeroInt(), + }, + { + name: "mints community pool inflation and staking rewards", + blockTime: 6, + communityPoolInflation: sdk.NewDecWithPrec(50, 2), + stakingRewardsApy: sdk.NewDecWithPrec(20, 2), + bondedRatio: sdk.NewDecWithPrec(35, 2), + // 50% APY for 6 seconds + // total supply = 1e10 + // https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.5%2C31536000%5D%5C%2841%29%2C6%5D-1%5C%2841%29*1e10 + // => 771.43 => truncated to 771 tokens. + expCommunityPoolBalance: sdk.NewInt(771), + // 20% APY for 6 seconds + // total bonded = 1e10 * 35% + // https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.20%2C31536000%5D%5C%2841%29%2C6%5D-1%5C%2841%29*1e10*.35 + // => 121.41 => truncated to 121 tokens. + expFeeCollectorBalance: sdk.NewInt(121), + }, + { + name: "handles extra long block time", + blockTime: 60, // like if we're upgrading the network & it takes an hour to get back up + communityPoolInflation: sdk.NewDecWithPrec(50, 2), + stakingRewardsApy: sdk.NewDecWithPrec(20, 2), + bondedRatio: sdk.NewDecWithPrec(35, 2), + // https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.5%2C31536000%5D%5C%2841%29%2C60%5D-1%5C%2841%29*1e10 + expCommunityPoolBalance: sdk.NewInt(7714), + // https://www.wolframalpha.com/input?i2d=true&i=%5C%2840%29Power%5B%5C%2840%29Surd%5B1.20%2C31536000%5D%5C%2841%29%2C60%5D-1%5C%2841%29*1e10*.35 + expFeeCollectorBalance: sdk.NewInt(1214), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + // set store and params + suite.Keeper.SetPreviousBlockTime(suite.Ctx, suite.Ctx.BlockTime()) + suite.Keeper.SetParams( + suite.Ctx, + types.NewParams(tc.communityPoolInflation, tc.stakingRewardsApy), + ) + + // set bonded token ratio + suite.SetBondedTokenRatio(tc.bondedRatio) + staking.EndBlocker(suite.Ctx, suite.StakingKeeper) + + // run begin blocker + kavamint.BeginBlocker(suite.Ctx, suite.Keeper) + + // expect everything empty to start + suite.CheckFeeCollectorBalance(suite.Ctx, sdk.ZeroInt()) + suite.CheckKavamintBalance(suite.Ctx, sdk.ZeroInt()) + suite.CheckCommunityPoolBalance(suite.Ctx, sdk.ZeroInt()) + + // expect initial block time set + startBlockTime := suite.Keeper.GetPreviousBlockTime(suite.Ctx) + suite.Require().False(startBlockTime.IsZero()) + suite.Require().Equal(suite.Ctx.BlockTime(), startBlockTime) + + // run begin blocker again to mint inflation + ctx2 := suite.Ctx.WithBlockTime(suite.Ctx.BlockTime().Add(time.Second * time.Duration(tc.blockTime))) + kavamint.BeginBlocker(ctx2, suite.Keeper) + + // check expected balances + suite.CheckCommunityPoolBalance(ctx2, tc.expCommunityPoolBalance) + suite.CheckFeeCollectorBalance(ctx2, tc.expFeeCollectorBalance) + + // x/kavamint balance should always be 0 because 100% should be transferred out every block + suite.CheckKavamintBalance(ctx2, sdk.ZeroInt()) + + // expect time to be updated + endBlockTime := suite.Keeper.GetPreviousBlockTime(ctx2) + suite.Require().False(endBlockTime.IsZero()) + suite.Require().Equal(ctx2.BlockTime(), endBlockTime) + }) + } +} + +func (suite *abciTestSuite) Test_BeginBlocker_DefaultsToBlockTime() { + suite.SetupTest() + + // unset previous block time + suite.Keeper.SetPreviousBlockTime(suite.Ctx, time.Time{}) + + // run begin blocker + kavamint.BeginBlocker(suite.Ctx, suite.Keeper) + + // ensure block time gets set + blockTime := suite.Keeper.GetPreviousBlockTime(suite.Ctx) + suite.False(blockTime.IsZero()) + suite.Equal(suite.Ctx.BlockTime(), blockTime) +} diff --git a/x/kavamint/client/cli/query.go b/x/kavamint/client/cli/query.go new file mode 100644 index 00000000..62a085e8 --- /dev/null +++ b/x/kavamint/client/cli/query.go @@ -0,0 +1,89 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/kava-labs/kava/x/kavamint/types" +) + +// GetQueryCmd returns the cli query commands for the minting module. +func GetQueryCmd() *cobra.Command { + mintingQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the minting module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + mintingQueryCmd.AddCommand( + GetCmdQueryParams(), + GetCmdQueryInflation(), + ) + + return mintingQueryCmd +} + +// GetCmdQueryParams implements a command to return the current minting +// parameters. +func GetCmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the current minting parameters", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + params := &types.QueryParamsRequest{} + res, err := queryClient.Params(cmd.Context(), params) + + if err != nil { + return err + } + + return clientCtx.PrintProto(&res.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryInflation implements a command to return the current minting +// inflation value. +func GetCmdQueryInflation() *cobra.Command { + cmd := &cobra.Command{ + Use: "inflation", + Short: "Query the current cumulative minting inflation value", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + queryClient := types.NewQueryClient(clientCtx) + + params := &types.QueryInflationRequest{} + res, err := queryClient.Inflation(cmd.Context(), params) + + if err != nil { + return err + } + + return clientCtx.PrintString(fmt.Sprintf("%s\n", res.Inflation)) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/kavamint/genesis.go b/x/kavamint/genesis.go new file mode 100644 index 00000000..8b29f9a2 --- /dev/null +++ b/x/kavamint/genesis.go @@ -0,0 +1,38 @@ +package kavamint + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/kava-labs/kava/x/kavamint/keeper" + "github.com/kava-labs/kava/x/kavamint/types" +) + +// InitGenesis new mint genesis +func InitGenesis(ctx sdk.Context, keeper keeper.Keeper, ak types.AccountKeeper, gs *types.GenesisState) { + // guard against invalid genesis + if err := gs.Validate(); err != nil { + panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err)) + } + + // get module account -- creates one with allowed permissions if it does not exist + macc := ak.GetModuleAccount(ctx, types.ModuleName) + if macc == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) + } + + // check module account has minter permissions + if !macc.HasPermission(authtypes.Minter) { + panic(fmt.Sprintf("%s module account does not have %s permissions", types.ModuleName, authtypes.Minter)) + } + + // set store state from genesis + keeper.SetParams(ctx, gs.Params) + keeper.SetPreviousBlockTime(ctx, gs.PreviousBlockTime) +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { + return types.NewGenesisState(keeper.GetParams(ctx), keeper.GetPreviousBlockTime(ctx)) +} diff --git a/x/kavamint/genesis_test.go b/x/kavamint/genesis_test.go new file mode 100644 index 00000000..1a6ef1ed --- /dev/null +++ b/x/kavamint/genesis_test.go @@ -0,0 +1,117 @@ +package kavamint_test + +import ( + "testing" + "time" + + "github.com/kava-labs/kava/x/kavamint" + "github.com/kava-labs/kava/x/kavamint/testutil" + "github.com/kava-labs/kava/x/kavamint/types" + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type genesisTestSuite struct { + testutil.KavamintTestSuite +} + +func (suite *genesisTestSuite) Test_InitGenesis_ValidationPanic() { + invalidState := types.NewGenesisState( + types.NewParams(sdk.OneDec(), types.MaxMintingRate.Add(sdk.OneDec())), // rate over max + time.Time{}, + ) + + suite.Panics(func() { + kavamint.InitGenesis(suite.Ctx, suite.Keeper, suite.App.GetAccountKeeper(), invalidState) + }, "expected init genesis to panic with invalid state") +} + +func (suite *genesisTestSuite) Test_InitGenesis_ModuleAccountDoesNotHaveMinterPerms() { + gs := types.DefaultGenesisState() + + ak := suite.App.GetAccountKeeper() + macc := ak.GetModuleAccount(suite.Ctx, types.ModuleName) + suite.Require().NotNil(macc) + + m, ok := macc.(*authtypes.ModuleAccount) + suite.Require().True(ok) + + m.Permissions = []string{} + ak.SetAccount(suite.Ctx, m) + + suite.PanicsWithValue("kavamint module account does not have minter permissions", func() { + kavamint.InitGenesis(suite.Ctx, suite.Keeper, suite.App.GetAccountKeeper(), gs) + }) +} + +func (suite *genesisTestSuite) Test_InitGenesis_CreatesModuleAccountWithPermissions() { + gs := types.DefaultGenesisState() + ak := suite.App.GetAccountKeeper() + + kavamint.InitGenesis(suite.Ctx, suite.Keeper, ak, gs) + + // by pass auto creation of module accounts + addr, _ := ak.GetModuleAddressAndPermissions(types.ModuleName) + acc := suite.App.GetAccountKeeper().GetAccount(suite.Ctx, addr) + suite.Require().NotNil(acc) + + macc, ok := acc.(authtypes.ModuleAccountI) + suite.Require().True(ok) + + suite.True(macc.HasPermission(authtypes.Minter)) +} + +func (suite *genesisTestSuite) Test_InitAndExportGenesis_DefaultValues() { + state := types.DefaultGenesisState() + + kavamint.InitGenesis(suite.Ctx, suite.Keeper, suite.App.GetAccountKeeper(), state) + + suite.Equal(types.DefaultParams(), suite.Keeper.GetParams(suite.Ctx), "expected default params to bet set in store") + + storeTime := suite.Keeper.GetPreviousBlockTime(suite.Ctx) + suite.Equal(types.DefaultPreviousBlockTime, storeTime, "expected default previous block time to be set in store") + + exportedState := kavamint.ExportGenesis(suite.Ctx, suite.Keeper) + suite.Equal(state, exportedState, "expected exported state to match imported state") +} + +func (suite *genesisTestSuite) Test_InitAndExportGenesis_SetValues() { + prevBlockTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC) + state := types.NewGenesisState( + types.NewParams(sdk.MustNewDecFromStr("0.000000000000000001"), sdk.MustNewDecFromStr("0.000000000000000002")), + prevBlockTime, + ) + + kavamint.InitGenesis(suite.Ctx, suite.Keeper, suite.App.GetAccountKeeper(), state) + + suite.Equal(state.Params, suite.Keeper.GetParams(suite.Ctx), "expected params to bet set in store") + + storeTime := suite.Keeper.GetPreviousBlockTime(suite.Ctx) + suite.Equal(state.PreviousBlockTime, storeTime, "expected previous block time to be set in store") + + exportedState := kavamint.ExportGenesis(suite.Ctx, suite.Keeper) + suite.Equal(state, exportedState, "expected exported state to match imported state") +} + +func (suite *genesisTestSuite) Test_InitAndExportGenesis_ZeroValues() { + state := types.NewGenesisState( + types.NewParams(sdk.ZeroDec(), sdk.ZeroDec()), + time.Time{}, + ) + + kavamint.InitGenesis(suite.Ctx, suite.Keeper, suite.App.GetAccountKeeper(), state) + + suite.Equal(state.Params, suite.Keeper.GetParams(suite.Ctx), "expected params to bet set in store") + + storeTime := suite.Keeper.GetPreviousBlockTime(suite.Ctx) + suite.Equal(state.PreviousBlockTime, storeTime, "expected previous block time to be set in store") + + exportedState := kavamint.ExportGenesis(suite.Ctx, suite.Keeper) + suite.Equal(state, exportedState, "expected exported state to match imported state") +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(genesisTestSuite)) +} diff --git a/x/kavamint/keeper/grpc_query.go b/x/kavamint/keeper/grpc_query.go new file mode 100644 index 00000000..ba910c98 --- /dev/null +++ b/x/kavamint/keeper/grpc_query.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/kavamint/types" +) + +var _ types.QueryServer = Keeper{} + +// Params returns params of the mint module. +func (k Keeper) Params(c context.Context, _ *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + params := k.GetParams(ctx) + + return &types.QueryParamsResponse{Params: params}, nil +} + +// Inflation returns minter.Inflation of the mint module. +func (k Keeper) Inflation(c context.Context, _ *types.QueryInflationRequest) (*types.QueryInflationResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + inflation := k.CumulativeInflation(ctx) + + return &types.QueryInflationResponse{Inflation: inflation}, nil +} diff --git a/x/kavamint/keeper/grpc_query_test.go b/x/kavamint/keeper/grpc_query_test.go new file mode 100644 index 00000000..ae81e639 --- /dev/null +++ b/x/kavamint/keeper/grpc_query_test.go @@ -0,0 +1,121 @@ +package keeper_test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/staking" + + "github.com/kava-labs/kava/x/kavamint/testutil" + "github.com/kava-labs/kava/x/kavamint/types" +) + +type grpcQueryTestSuite struct { + testutil.KavamintTestSuite + + queryClient types.QueryClient +} + +func (suite *grpcQueryTestSuite) SetupTest() { + suite.KavamintTestSuite.SetupTest() + + queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry()) + types.RegisterQueryServer(queryHelper, suite.Keeper) + suite.queryClient = types.NewQueryClient(queryHelper) +} + +func TestGRPCQueryTestSuite(t *testing.T) { + suite.Run(t, new(grpcQueryTestSuite)) +} + +func (suite *grpcQueryTestSuite) TestGRPCQueryParams() { + app, ctx, queryClient := suite.App, suite.Ctx, suite.queryClient + + kavamintKeeper := app.GetKavamintKeeper() + + params, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{}) + suite.Require().NoError(err) + suite.Require().Equal(params.Params, kavamintKeeper.GetParams(ctx)) +} + +func (suite *grpcQueryTestSuite) TestGRPCInflationQuery() { + testCases := []struct { + name string + communityInflation sdk.Dec + stakingApy sdk.Dec + bondedRatio sdk.Dec + expectedInflation sdk.Dec + }{ + { + name: "no community inflation, no staking apy = no inflation", + communityInflation: sdk.NewDec(0), + stakingApy: sdk.NewDec(0), + bondedRatio: sdk.NewDecWithPrec(40, 2), + expectedInflation: sdk.NewDec(0), + }, + { + name: "no community inflation means only staking contributes", + communityInflation: sdk.NewDec(0), + stakingApy: sdk.NewDec(1), + bondedRatio: sdk.NewDecWithPrec(34, 2), + expectedInflation: sdk.NewDecWithPrec(34, 2), + }, + { + name: "no staking apy means only inflation contributes", + communityInflation: sdk.NewDecWithPrec(75, 2), + stakingApy: sdk.NewDec(0), + bondedRatio: sdk.NewDecWithPrec(40, 2), + expectedInflation: sdk.NewDecWithPrec(75, 2), + }, + { + name: "staking and community inflation combines (100 percent bonded)", + communityInflation: sdk.NewDec(1), + stakingApy: sdk.NewDecWithPrec(50, 2), + bondedRatio: sdk.NewDec(1), + expectedInflation: sdk.NewDecWithPrec(150, 2), + }, + { + name: "staking and community inflation combines (40 percent bonded)", + communityInflation: sdk.NewDecWithPrec(90, 2), + stakingApy: sdk.NewDecWithPrec(25, 2), + bondedRatio: sdk.NewDecWithPrec(40, 2), + // 90 + .4*25 = 100 + expectedInflation: sdk.NewDec(1), + }, + { + name: "staking and community inflation combines (25 percent bonded)", + communityInflation: sdk.NewDecWithPrec(90, 2), + stakingApy: sdk.NewDecWithPrec(20, 2), + bondedRatio: sdk.NewDecWithPrec(25, 2), + // 90 + .25*20 = 95 + expectedInflation: sdk.NewDecWithPrec(95, 2), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupTest() + + app, ctx, queryClient := suite.App, suite.Ctx, suite.queryClient + + kavamintKeeper := app.GetKavamintKeeper() + + // set desired params + kavamintKeeper.SetParams(ctx, types.NewParams(tc.communityInflation, tc.stakingApy)) + + // set bonded token ratio + suite.SetBondedTokenRatio(tc.bondedRatio) + staking.EndBlocker(ctx, suite.StakingKeeper) + + // query inflation & check for expected results + inflation, err := queryClient.Inflation(context.Background(), &types.QueryInflationRequest{}) + suite.Require().NoError(err) + suite.Require().Equal(inflation.Inflation, kavamintKeeper.CumulativeInflation(ctx)) + suite.Require().Equal(inflation.Inflation, tc.expectedInflation) + }) + } +} diff --git a/x/kavamint/keeper/inflation.go b/x/kavamint/keeper/inflation.go new file mode 100644 index 00000000..d0c260ed --- /dev/null +++ b/x/kavamint/keeper/inflation.go @@ -0,0 +1,54 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// this is the same value used in the x/hard +const ( + SecondsPerYear = uint64(31536000) +) + +// AccumulateInflation calculates the number of coins that should be minted to match a yearly `rate` +// for interest compounded each second of the year over `secondsSinceLastMint` seconds. +// `basis` is the base amount of coins that is inflated. +func (k Keeper) AccumulateInflation( + ctx sdk.Context, + rate sdk.Dec, + basis sdk.Int, + secondsSinceLastMint float64, +) (sdk.Coins, error) { + bondDenom := k.BondDenom(ctx) + + // calculate the rate factor based on apy & seconds passed since last block + inflationRate, err := CalculateInflationRate(rate, uint64(secondsSinceLastMint)) + if err != nil { + return sdk.NewCoins(), err + } + + amount := inflationRate.MulInt(basis).TruncateInt() + + return sdk.NewCoins(sdk.NewCoin(bondDenom, amount)), nil +} + +// CalculateInflationRate converts an APY into the factor corresponding with that APY's accumulation +// over a period of secondsPassed seconds. +func CalculateInflationRate(apy sdk.Dec, secondsPassed uint64) (sdk.Dec, error) { + perSecondInterestRate, err := apyToSpy(apy.Add(sdk.OneDec())) + if err != nil { + return sdk.ZeroDec(), err + } + rate := perSecondInterestRate.Power(secondsPassed) + return rate.Sub(sdk.OneDec()), nil +} + +// apyToSpy converts the input annual interest rate. For example, 10% apy would be passed as 1.10. +// SPY = Per second compounded interest rate is how cosmos mathematically represents APY. +func apyToSpy(apy sdk.Dec) (sdk.Dec, error) { + // Note: any APY greater than 176.5 will cause an out-of-bounds error + root, err := apy.ApproxRoot(SecondsPerYear) + if err != nil { + return sdk.ZeroDec(), err + } + return root, nil +} diff --git a/x/kavamint/keeper/inflation_test.go b/x/kavamint/keeper/inflation_test.go new file mode 100644 index 00000000..21029460 --- /dev/null +++ b/x/kavamint/keeper/inflation_test.go @@ -0,0 +1,142 @@ +package keeper_test + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/kavamint/keeper" + "github.com/kava-labs/kava/x/kavamint/types" + "github.com/stretchr/testify/suite" +) + +type InflationTestSuite struct { + suite.Suite +} + +func TestInflationTestSuite(t *testing.T) { + suite.Run(t, new(InflationTestSuite)) +} + +func (suite *InflationTestSuite) TestCalculateInflationFactor() { + testCases := []struct { + name string + apy sdk.Dec + secondsPassed uint64 + expectedRate sdk.Dec + // preciseToDecimal is the decimal place after which error is present. if >=18, number is exact. + // ex. precise to 3 decimals means accurate to +/-0.0005 + preciseToDecimal int + }{ + { + name: "any apy over 0 seconds is 0", + apy: sdk.OneDec(), + secondsPassed: 0, + expectedRate: sdk.ZeroDec(), + preciseToDecimal: 19, + }, + { + name: "zero apy for any number of seconds is 0", + apy: sdk.ZeroDec(), + secondsPassed: 100, + expectedRate: sdk.ZeroDec(), + preciseToDecimal: 19, + }, + { + name: "an apy over a year's worth of seconds should be the apy", + apy: sdk.NewDecWithPrec(10, 2), + secondsPassed: keeper.SecondsPerYear, + expectedRate: sdk.NewDecWithPrec(10, 2), + preciseToDecimal: 10, + }, + { + name: "example: 22 percent for 6 seconds, precise to 17 decimals", + apy: sdk.NewDecWithPrec(22, 2), + secondsPassed: 6, + expectedRate: sdk.MustNewDecFromStr("0.000000037833116915"), + preciseToDecimal: 17, + }, + { + name: "example: 3 percent for 10 seconds, precise to 17 decimals", + apy: sdk.NewDecWithPrec(3, 2), + secondsPassed: 10, + expectedRate: sdk.MustNewDecFromStr("0.000000009373034748"), + preciseToDecimal: 17, + }, + { + name: "example: 150 percent for 10 seconds, precise to 17 decimals", + apy: sdk.NewDecWithPrec(150, 2), + secondsPassed: 10, + expectedRate: sdk.MustNewDecFromStr("0.000000290553927255"), + preciseToDecimal: 17, + }, + { + name: "example: 10 percent for 10 seconds, precise to 16 decimals", + apy: sdk.NewDecWithPrec(10, 2), + secondsPassed: 10, + expectedRate: sdk.MustNewDecFromStr("0.000000030222660212"), + preciseToDecimal: 16, + }, + { + name: "example: 1,000 percent for 10 seconds, precise to 16 decimals", + apy: sdk.NewDecWithPrec(1000, 2), + secondsPassed: 10, + expectedRate: sdk.MustNewDecFromStr("0.000000760367892072"), + preciseToDecimal: 16, + }, + { + name: "example: 10 percent for 30 seconds, precise to 16 decimals", + apy: sdk.NewDecWithPrec(10, 2), + secondsPassed: 30, + expectedRate: sdk.MustNewDecFromStr("0.00000009066798338"), + preciseToDecimal: 16, + }, + { + name: "example: 95 percent for 15 seconds, precise to 16 decimals", + apy: sdk.NewDecWithPrec(95, 2), + secondsPassed: 15, + expectedRate: sdk.MustNewDecFromStr("0.000000317651007726"), + preciseToDecimal: 16, + }, + { + name: "example: 10,000 percent for 10 seconds, precise to 13 decimals", + apy: sdk.NewDecWithPrec(10000, 2), + secondsPassed: 10, + expectedRate: sdk.MustNewDecFromStr("0.000001463446186527"), + preciseToDecimal: 13, + }, + { + name: "can handle upper bound of APY (but w/ large error)", + apy: types.MaxMintingRate, + secondsPassed: 10, + expectedRate: sdk.MustNewDecFromStr("0.000001642242155465"), + preciseToDecimal: 4, // NOTE: error is really large here + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + actualRate, err := keeper.CalculateInflationRate(tc.apy, tc.secondsPassed) + suite.Require().NoError(err) + + marginOfError := sdk.ZeroDec() + if tc.preciseToDecimal < 18 { + marginOfError = sdk.NewDecWithPrec(5, int64(tc.preciseToDecimal+1)) + } + suite.requireWithinError(tc.expectedRate, actualRate, marginOfError) + }) + } + + // TODO: nick bring back + //suite.Run("errors when rate is too high", func() { + // _, err := keeper.CalculateInflationRate(types.MaxMintingRate.Add(sdk.OneDec()), 100) + // suite.Error(err) + //}) +} + +func (suite *InflationTestSuite) requireWithinError(expected, actual, margin sdk.Dec) { + suite.Require().Truef( + actual.Sub(expected).Abs().LTE(margin), + fmt.Sprintf("precision is outside desired margin of error %s\nexpected: %s\nactual : %s", margin, expected, actual), + ) +} diff --git a/x/kavamint/keeper/keeper.go b/x/kavamint/keeper/keeper.go new file mode 100644 index 00000000..e29c81ad --- /dev/null +++ b/x/kavamint/keeper/keeper.go @@ -0,0 +1,178 @@ +package keeper + +import ( + "time" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/kava-labs/kava/x/kavamint/types" +) + +type KeeperI interface { + GetParams(ctx sdk.Context) (params types.Params) + SetParams(ctx sdk.Context, params types.Params) + + BondDenom(ctx sdk.Context) string + TotalSupply(ctx sdk.Context) sdk.Int + TotalBondedTokens(ctx sdk.Context) sdk.Int + + MintCoins(ctx sdk.Context, newCoins sdk.Coins) error + AddCollectedFees(ctx sdk.Context, fees sdk.Coins) error + FundCommunityPool(ctx sdk.Context, funds sdk.Coins) error + + GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time) + SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) + + CumulativeInflation(ctx sdk.Context) sdk.Dec + AccumulateInflation( + ctx sdk.Context, + rate sdk.Dec, + basis sdk.Int, + secondsSinceLastMint float64, + ) (sdk.Coins, error) +} + +// Keeper of the kavamint store +type Keeper struct { + cdc codec.BinaryCodec + storeKey sdk.StoreKey + paramSpace paramtypes.Subspace + stakingKeeper types.StakingKeeper + bankKeeper types.BankKeeper + stakingRewardsFeeCollectorName string + communityPoolModuleAccountName string +} + +var _ KeeperI = Keeper{} + +// NewKeeper creates a new kavamint Keeper instance +func NewKeeper( + cdc codec.BinaryCodec, key sdk.StoreKey, paramSpace paramtypes.Subspace, + sk types.StakingKeeper, ak types.AccountKeeper, bk types.BankKeeper, + stakingRewardsFeeCollectorName string, communityPoolModuleAccountName string, +) Keeper { + // set KeyTable if it has not already been set + if !paramSpace.HasKeyTable() { + paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) + } + + return Keeper{ + cdc: cdc, + storeKey: key, + paramSpace: paramSpace, + stakingKeeper: sk, + bankKeeper: bk, + stakingRewardsFeeCollectorName: stakingRewardsFeeCollectorName, + communityPoolModuleAccountName: communityPoolModuleAccountName, + } +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+types.ModuleName) +} + +// GetParams returns the total set of minting parameters. +func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) { + k.paramSpace.GetParamSet(ctx, ¶ms) + return params +} + +// SetParams sets the total set of minting parameters. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { + k.paramSpace.SetParamSet(ctx, ¶ms) +} + +// BondDenom implements an alias call to the underlying staking keeper's BondDenom. +func (k Keeper) BondDenom(ctx sdk.Context) string { + return k.stakingKeeper.BondDenom(ctx) +} + +// TotalBondedTokens implements an alias call to the underlying staking keeper's +// TotalBondedTokens to be used in BeginBlocker. +func (k Keeper) TotalBondedTokens(ctx sdk.Context) sdk.Int { + return k.stakingKeeper.TotalBondedTokens(ctx) +} + +// MintCoins implements an alias call to the underlying supply keeper's +// MintCoins to be used in BeginBlocker. +func (k Keeper) MintCoins(ctx sdk.Context, newCoins sdk.Coins) error { + if newCoins.Empty() { + // skip as no coins need to be minted + return nil + } + + return k.bankKeeper.MintCoins(ctx, types.ModuleName, newCoins) +} + +// AddCollectedFees implements an alias call to the underlying supply keeper's +// AddCollectedFees to be used in BeginBlocker. +func (k Keeper) AddCollectedFees(ctx sdk.Context, fees sdk.Coins) error { + return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.stakingRewardsFeeCollectorName, fees) +} + +// FundCommunityPool implements an alias call to the underlying supply keeper's +// FundCommunityPool to be used in BeginBlocker. +func (k Keeper) FundCommunityPool(ctx sdk.Context, funds sdk.Coins) error { + return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.communityPoolModuleAccountName, funds) +} + +// TotalSupply implements an alias call to the underlying supply keeper's +// GetSupply for the mint denom to be used in calculating cumulative inflation. +func (k Keeper) TotalSupply(ctx sdk.Context) sdk.Int { + return k.bankKeeper.GetSupply(ctx, k.BondDenom(ctx)).Amount +} + +func (k Keeper) CumulativeInflation(ctx sdk.Context) sdk.Dec { + params := k.GetParams(ctx) + totalInflation := sdk.NewDec(0) + + // community pool contribution is simply the inflation param + totalInflation = totalInflation.Add(params.CommunityPoolInflation) + + // staking rewards contribution is the apy * bonded_ratio + bondedSupply := k.TotalBondedTokens(ctx) + totalSupply := k.TotalSupply(ctx) + bondedRatio := sdk.NewDecFromInt(bondedSupply).QuoInt(totalSupply) + inflationFromStakingRewards := params.StakingRewardsApy.Mul(bondedRatio) + + totalInflation = totalInflation.Add(inflationFromStakingRewards) + + return totalInflation +} + +// GetPreviousBlockTime get the blocktime for the previous block +func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PreviousBlockTimeKey) + + b := store.Get(types.PreviousBlockTimeKey) + if b == nil { + return blockTime + } + + if err := blockTime.UnmarshalBinary(b); err != nil { + panic(err) + } + + return +} + +// SetPreviousBlockTime set the time of the previous block +func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.PreviousBlockTimeKey) + + if blockTime.IsZero() { + store.Delete(types.PreviousBlockTimeKey) + return + } + + b, err := blockTime.MarshalBinary() + if err != nil { + panic(err) + } + store.Set(types.PreviousBlockTimeKey, b) +} diff --git a/x/kavamint/keeper/keeper_test.go b/x/kavamint/keeper/keeper_test.go new file mode 100644 index 00000000..c3474b86 --- /dev/null +++ b/x/kavamint/keeper/keeper_test.go @@ -0,0 +1,53 @@ +package keeper_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/kavamint/testutil" + "github.com/kava-labs/kava/x/kavamint/types" + "github.com/stretchr/testify/suite" +) + +type keeperTestSuite struct { + testutil.KavamintTestSuite +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(keeperTestSuite)) +} + +func (suite keeperTestSuite) TestParams_Persistance() { + keeper := suite.Keeper + + params := types.NewParams( + sdk.MustNewDecFromStr("0.000000000000000001"), + sdk.MustNewDecFromStr("0.000000000000000002"), + ) + keeper.SetParams(suite.Ctx, params) + suite.Equal(keeper.GetParams(suite.Ctx), params) + + oldParams := params + params = types.NewParams( + sdk.MustNewDecFromStr("0.000000000000000011"), + sdk.MustNewDecFromStr("0.000000000000000022"), + ) + keeper.SetParams(suite.Ctx, params) + suite.NotEqual(keeper.GetParams(suite.Ctx), oldParams) + suite.Equal(keeper.GetParams(suite.Ctx), params) +} + +func (suite keeperTestSuite) TestPreviousBlockTime_Persistance() { + keeper := suite.Keeper + zeroTime := time.Time{} + + keeper.SetPreviousBlockTime(suite.Ctx, zeroTime) + suite.Equal(keeper.GetPreviousBlockTime(suite.Ctx), zeroTime) + + newTime := suite.Ctx.BlockTime() + suite.Require().False(newTime.IsZero()) + + keeper.SetPreviousBlockTime(suite.Ctx, newTime) + suite.Equal(keeper.GetPreviousBlockTime(suite.Ctx), newTime) +} diff --git a/x/kavamint/module.go b/x/kavamint/module.go new file mode 100644 index 00000000..58d6c255 --- /dev/null +++ b/x/kavamint/module.go @@ -0,0 +1,152 @@ +package kavamint + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/kava-labs/kava/x/kavamint/client/cli" + "github.com/kava-labs/kava/x/kavamint/keeper" + "github.com/kava-labs/kava/x/kavamint/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// AppModuleBasic defines the basic application module used by the kavamint module. +type AppModuleBasic struct { + cdc codec.Codec +} + +var _ module.AppModuleBasic = AppModuleBasic{} + +// Name returns the mint module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the kavamint module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {} + +// RegisterInterfaces registers the module's interface types +func (b AppModuleBasic) RegisterInterfaces(_ cdctypes.InterfaceRegistry) {} + +// DefaultGenesis returns default genesis state as raw bytes for the mint +// module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the kavamint module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return gs.Validate() +} + +// RegisterRESTRoutes registers the REST routes for the kavamint module. +func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the kavamint module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)) + +} + +// GetTxCmd returns no root tx command for the kavamint module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } + +// GetQueryCmd returns the root query command for the kavamint module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule implements an application module for the kavamint module. +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + authKeeper types.AccountKeeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, ak types.AccountKeeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + authKeeper: ak, + } +} + +// Name returns the kavamint module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants registers the kavamint module invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route returns the message routing key for the kavamint module. +func (AppModule) Route() sdk.Route { return sdk.Route{} } + +// QuerierRoute returns the kavamint module's querier route name. +func (AppModule) QuerierRoute() string { + return types.QuerierRoute +} + +// LegacyQuerierHandler returns the kavamint module sdk.Querier. +func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { + return nil +} + +// RegisterServices registers a gRPC query service to respond to the +// module-specific gRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// InitGenesis performs genesis initialization for the kavamint module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState types.GenesisState + cdc.MustUnmarshalJSON(data, &genesisState) + + InitGenesis(ctx, am.keeper, am.authKeeper, &genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the mint +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock returns the begin blocker for the kavamint module. +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { + BeginBlocker(ctx, am.keeper) +} + +// EndBlock returns the end blocker for the kavamint module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/kavamint/module_test.go b/x/kavamint/module_test.go new file mode 100644 index 00000000..f7a070e7 --- /dev/null +++ b/x/kavamint/module_test.go @@ -0,0 +1,31 @@ +package kavamint_test + +import ( + "testing" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/kavamint/types" +) + +func TestKavaMintModuleAccountWithPermissionsOnAppInit(t *testing.T) { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, tmproto.Header{Height: 1}) + tApp.InitializeFromGenesisStates() + + ak := tApp.GetAccountKeeper() + + // by pass auto creation of module accounts + addr, _ := ak.GetModuleAddressAndPermissions(types.ModuleName) + acc := ak.GetAccount(ctx, addr) + + require.NotNil(t, acc, "expected module account to exist") + + macc, ok := acc.(authtypes.ModuleAccountI) + require.True(t, ok) + + require.True(t, macc.HasPermission(authtypes.Minter), "expected module account to have mint permissions") +} diff --git a/x/kavamint/spec/01_concepts.md b/x/kavamint/spec/01_concepts.md new file mode 100644 index 00000000..1314053f --- /dev/null +++ b/x/kavamint/spec/01_concepts.md @@ -0,0 +1,45 @@ + + +# Concepts + +## Singular Source of Inflation + +The kavamint module mints new tokens each block. How many tokens are minted is determined by rates set in kavamint's parameters. Where the newly minted tokens are transferred to is dictated by which parameter. The kavamint module supports minting for the following purposes: + +| Purpose | Parameter | Rate Based On | Transferred To | +| --------------- | ------------------------ | ----------------------- | --------------- | +| Community Pool | `CommunityPoolInflation` | TotalSupply | x/community | +| Staking Rewards | `StakingRewardsApy` | TotalSupply*BondedRatio | x/auth fee pool | + +`TotalSupply` is the total number of tokens. `BondedRatio` is the percentage of those tokens that are delegated to a bonded validator. + +### How Amount is Calculated + +The number of seconds since the previous block is determined: `secondsSinceLastBlock`. + +For each yearly rate set in the parameters, an equivalent rate for the number of seconds passed is determined assuming the rate compounds every second and, for simplicity, there are a fixed number of seconds per year (`365 x 24 x 60 x 60 = 31,536,000`). The rate compounded over `secondsSinceLastBlock` seconds is the `31,536,000`th root of `rate + 1` to the `secondsSinceLastBlock` power (minus 100% to determine number of new tokens). This number is truncated to an `sdk.Int`. + +**example**: +Assume `CommunityPoolInflation` is 20%, the `TotalSupply` is 1e10 tokens and `secondsSinceLastBlock` is 5 seconds. The number of tokens minted is determined to be 289 tokens. + +```math +({\sqrt[secondsPerYear]{1 + CommunityPoolInflation}}^{secondsSinceLastBlock} - 1)*TotalSupply += ({\sqrt[31,536,000]{1.2}}^{5} - 1)*1e10 +\approx 289 +``` + +## Staking Reward Distribution + +It helps to understand how newly minted tokens are created and distributed as validator rewards by default in a Cosmos SDK blockchain. In the standard Cosmos SDK mint module, tokens are minted each block based on a variable inflation rate determined by the parameters and the desired bonded token ratio. The tokens are then transferred to the auth module's fee pool to be distributed as staking rewards after a portion of the total fee pool is transferred to the community pool (determined by the CommunityTax parameter of the distribution module). + +The kavamint module separates the default community pool inflation from staking rewards. For staking rewards, it uses an APY determined by StakingRewardsApy. The basis is the total bonded tokens. The tokens are distributed to the same fee pool as above so they can be distributed as rewards to delegators with the default distribution module. + +## Community Pool Inflation + +Every block, tokens are distributed to KAVA's community pool, the module account of x/community. The number of tokens distributed is determined by the total supply. + +## Disabling Default Inflation + +The intention of this module is to replace the default minting of the x/mint module. To make that the case, the minimum and maximum inflation parameters of the mint module should be set to 0. Additionally, it's recommended that the community tax parameter of the distribution module should be set to 0 as the logic for funding the community pool is controlled by kavamint. diff --git a/x/kavamint/spec/02_state.md b/x/kavamint/spec/02_state.md new file mode 100644 index 00000000..91a8e1df --- /dev/null +++ b/x/kavamint/spec/02_state.md @@ -0,0 +1,36 @@ + + +# State + +## Parameters and Genesis State + +`Parameters` define the governance parameters that control inflation of KAVA. +Each parameter is an APY/inflation used in conjunction with the block time to +calculate how much KAVA to mint for what purposes. See [params](./05_params.md) for a description of each param. + +```go +// Params wraps the governance parameters for the kavamint module +type Params struct { + // yearly inflation of total token supply minted to the community pool. + CommunityPoolInflation sdk.Dec `json:"community_pool_inflation" yaml:"community_pool_inflation"` + // yearly inflation of bonded tokens minted for staking rewards to validators. + StakingRewardsApy sdk.Dec `json:"staking_rewards_apy" yaml:"staking_rewards_apy"` +} +``` + +`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the kavamint module to resume. + +```go + +// GenesisState defines the kavamint module's genesis state. +type GenesisState struct { + // params defines all the parameters of the module. + Params Params `json:"params" yaml:"params"` +} +``` + +## Previous Block Time + +The `PreviousBlockTime` is stored in the keeper and updated each block. In the BeginBlocker, the number of seconds between the current and previous block is determined and used to calculate how much KAVA should be minted for each diff --git a/x/kavamint/spec/03_messages.md b/x/kavamint/spec/03_messages.md new file mode 100644 index 00000000..6b466303 --- /dev/null +++ b/x/kavamint/spec/03_messages.md @@ -0,0 +1,7 @@ + + +# Messages + +The kavadist module contains no messages. All state transitions are controlled by parameters, which can be updated via parameter change governance proposals. diff --git a/x/kavamint/spec/04_events.md b/x/kavamint/spec/04_events.md new file mode 100644 index 00000000..45502156 --- /dev/null +++ b/x/kavamint/spec/04_events.md @@ -0,0 +1,17 @@ + + +# Events + +The kavamint module emits the following events: + +## BeginBlocker + +| Type | Attribute Key | Attribute Value | +| -------- | ------------------------------- | ------------------------ | +| kavamint | total_supply | {totalSupply} | +| kavamint | bonded_ratio | {bondedRatio} | +| kavamint | seconds_since_last_mint | {secondsPassed} | +| kavamint | minted_community_pool_inflation | {communityPoolInflation} | +| kavamint | minted_staking_rewards | {stakingRewardCoins} | diff --git a/x/kavamint/spec/05_params.md b/x/kavamint/spec/05_params.md new file mode 100644 index 00000000..33d1f490 --- /dev/null +++ b/x/kavamint/spec/05_params.md @@ -0,0 +1,12 @@ + + +# Parameters + +Example parameters for the kavamint module: + +| Key | Type | Example | Description | +| ---------------------- | ------- | ---------------------- | ------------------------------------------------------------------------ | +| CommunityPoolInflation | sdk.Dec | "0.250000000000000000" | Yearly inflation rate of total token supply minted to the community pool | +| StakingRewardsAPY | sdk.Dec | "0.200000000000000000" | APY of bonded tokens minted for staking reward distribution | diff --git a/x/kavamint/spec/06_begin_block.md b/x/kavamint/spec/06_begin_block.md new file mode 100644 index 00000000..6b104fa1 --- /dev/null +++ b/x/kavamint/spec/06_begin_block.md @@ -0,0 +1,49 @@ + + +# Begin Block + +At the start of each block, new KAVA tokens are minted and distributed + +```go +// BeginBlocker mints & distributes new tokens for the previous block. +func BeginBlocker(ctx sdk.Context, k Keeper) { + params := k.GetParams(ctx) + // fetch the last block time from state + previousBlockTime := k.GetPreviousBlockTime(ctx) + secondsPassed := ctx.BlockTime().Sub(previousBlockTime).Seconds() + + // determine totals before any new mints + totalSupply := k.TotalSupply(ctx) + totalBonded := k.TotalBondedTokens(ctx) + + // ------------- Staking Rewards ------------- + // determine amount of the bond denom to mint for staking rewards + stakingRewardCoins, err := k.AccumulateInflation( + ctx, params.StakingRewardsApy, totalBonded, secondsPassed, + ) + // mint the staking rewards + k.MintCoins(ctx, stakingRewardCoins) + // distribute them to the fee pool for distribution by x/distribution + k.AddCollectedFees(ctx, stakingRewardCoins) + + // ------------- Community Pool ------------- + // determine amount of the bond denom to mint for community pool inflation + communityPoolInflation, err := k.AccumulateInflation( + ctx, params.CommunityPoolInflation, totalSupply, secondsPassed, + ) + // mint the community pool tokens + k.MintCoins(ctx, communityPoolCoins) + // send them to the community module account (the community pool) + k.AddCommunityPoolFunds(ctx, communityPoolCoins) + + // ------------- Bookkeeping ------------- + // set block time for next iteration's minting + k.SetPreviousBlockTime(ctx, ctx.BlockTime()) +} +``` + +`AccumulateInflation` determines the effective rate of the yearly interest rate assuming it is +compounded once per second, for the number of seconds since the previous mint. See concepts for +more details. diff --git a/x/kavamint/spec/README.md b/x/kavamint/spec/README.md new file mode 100644 index 00000000..e6e1e731 --- /dev/null +++ b/x/kavamint/spec/README.md @@ -0,0 +1,21 @@ + + +# `kavamint` + + + +1. **[Concepts](01_concepts.md)** +2. **[State](02_state.md)** +3. **[Messages](03_messages.md)** +4. **[Events](04_events.md)** +5. **[Params](05_params.md)** +6. **[BeginBlock](06_begin_block.md)** + +## Abstract + +`x/kavamint` is an implementation of a Cosmos SDK Module that provides for functionality and governance of overall inflation of the KAVA staking token. diff --git a/x/kavamint/testutil/suite.go b/x/kavamint/testutil/suite.go new file mode 100644 index 00000000..87d6f8a6 --- /dev/null +++ b/x/kavamint/testutil/suite.go @@ -0,0 +1,61 @@ +package testutil + +import ( + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/kavamint/keeper" +) + +type KavamintTestSuite struct { + suite.Suite + + App app.TestApp + Ctx sdk.Context + Keeper keeper.Keeper + StakingKeeper stakingkeeper.Keeper + + BondDenom string +} + +func (suite *KavamintTestSuite) SetupTest() { + app.SetSDKConfig() + suite.App = app.NewTestApp() + suite.App.InitializeFromGenesisStates() + suite.Ctx = suite.App.BaseApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) + suite.Keeper = suite.App.GetKavamintKeeper() + suite.StakingKeeper = suite.App.GetStakingKeeper() + + suite.BondDenom = suite.Keeper.BondDenom(suite.Ctx) +} + +// SetBondedTokenRatio mints the total supply to an account and creates a validator with a self +// delegation that makes the total staked token ratio set as desired. +// EndBlocker must be run in order for tokens to become bonded. +// returns total supply coins +func (suite *KavamintTestSuite) SetBondedTokenRatio(ratio sdk.Dec) sdk.Coins { + address := app.RandomAddress() + + supplyAmount := sdk.NewInt(1e10) + totalSupply := sdk.NewCoins(sdk.NewCoin(suite.BondDenom, supplyAmount)) + amountToBond := ratio.MulInt(supplyAmount).TruncateInt() + + // fund account that will create validator with total supply + err := suite.App.FundAccount(suite.Ctx, address, totalSupply) + suite.Require().NoError(err) + + if ratio.IsZero() { + return totalSupply + } + + // create a validator with self delegation such that ratio is achieved + err = suite.App.CreateNewUnbondedValidator(suite.Ctx, sdk.ValAddress(address), amountToBond) + suite.Require().NoError(err) + + return totalSupply +} diff --git a/x/kavamint/types/events.go b/x/kavamint/types/events.go new file mode 100644 index 00000000..33a1bf60 --- /dev/null +++ b/x/kavamint/types/events.go @@ -0,0 +1,12 @@ +package types + +// Minting module event types +const ( + EventTypeMint = ModuleName + + AttributeKeyTotalSupply = "total_supply" + AttributeKeyTotalBonded = "total_bonded" + AttributeSecondsPassed = "seconds_since_last_mint" + AttributeKeyCommunityPoolMint = "minted_community_pool_inflation" + AttributeKeyStakingRewardMint = "minted_staking_rewards" +) diff --git a/x/kavamint/types/expected_keepers.go b/x/kavamint/types/expected_keepers.go new file mode 100644 index 00000000..db7941ee --- /dev/null +++ b/x/kavamint/types/expected_keepers.go @@ -0,0 +1,26 @@ +package types // noalias + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +// StakingKeeper defines the expected staking keeper +type StakingKeeper interface { + BondDenom(sdk.Context) string + TotalBondedTokens(sdk.Context) sdk.Int +} + +// AccountKeeper defines the contract required for account APIs. +type AccountKeeper interface { + GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx sdk.Context, moduleName string) types.ModuleAccountI +} + +// BankKeeper defines the contract needed to be fulfilled for banking and supply +// dependencies. +type BankKeeper interface { + GetSupply(ctx sdk.Context, denom string) sdk.Coin + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error +} diff --git a/x/kavamint/types/genesis.go b/x/kavamint/types/genesis.go new file mode 100644 index 00000000..e5ec4fb5 --- /dev/null +++ b/x/kavamint/types/genesis.go @@ -0,0 +1,32 @@ +package types + +import ( + time "time" +) + +var ( + // DefaultPreviousBlockTime represents a time that is unset -- no previous block has occured + DefaultPreviousBlockTime = time.Time{} +) + +// NewGenesisState creates a new GenesisState object +func NewGenesisState(params Params, previousBlockTime time.Time) *GenesisState { + return &GenesisState{ + Params: params, + PreviousBlockTime: previousBlockTime, + } +} + +// DefaultGenesisState creates a default GenesisState object +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + PreviousBlockTime: DefaultPreviousBlockTime, + } +} + +// Validate validates the provided genesis state to ensure the +// expected invariants holds. +func (gs GenesisState) Validate() error { + return gs.Params.Validate() +} diff --git a/x/kavamint/types/genesis.pb.go b/x/kavamint/types/genesis.pb.go new file mode 100644 index 00000000..6750080f --- /dev/null +++ b/x/kavamint/types/genesis.pb.go @@ -0,0 +1,372 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: kava/kavamint/v1beta1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the kavamint module's genesis state. +type GenesisState struct { + // Params defines all the parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + // PreviousBlockTime holds the last last time tokens were minted. + // On first block, tokens will be minted for total number of seconds passed since this time. + PreviousBlockTime time.Time `protobuf:"bytes,2,opt,name=previous_block_time,json=previousBlockTime,proto3,stdtime" json:"previous_block_time"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_179d81c8ae95ec37, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GenesisState)(nil), "kava.kavamint.v1beta1.GenesisState") +} + +func init() { + proto.RegisterFile("kava/kavamint/v1beta1/genesis.proto", fileDescriptor_179d81c8ae95ec37) +} + +var fileDescriptor_179d81c8ae95ec37 = []byte{ + // 286 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xce, 0x4e, 0x2c, 0x4b, + 0xd4, 0x07, 0x11, 0xb9, 0x99, 0x79, 0x25, 0xfa, 0x65, 0x86, 0x49, 0xa9, 0x25, 0x89, 0x86, 0xfa, + 0xe9, 0xa9, 0x79, 0xa9, 0xc5, 0x99, 0xc5, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xa2, 0x20, + 0x79, 0x3d, 0x98, 0x22, 0x3d, 0xa8, 0x22, 0x29, 0x91, 0xf4, 0xfc, 0xf4, 0x7c, 0xb0, 0x0a, 0x7d, + 0x10, 0x0b, 0xa2, 0x58, 0x4a, 0x3e, 0x3d, 0x3f, 0x3f, 0x3d, 0x27, 0x55, 0x1f, 0xcc, 0x4b, 0x2a, + 0x4d, 0xd3, 0x2f, 0xc9, 0xcc, 0x4d, 0x2d, 0x2e, 0x49, 0xcc, 0x2d, 0x80, 0x2a, 0x50, 0xc1, 0x6e, + 0x25, 0xdc, 0x78, 0xb0, 0x2a, 0xa5, 0xe5, 0x8c, 0x5c, 0x3c, 0xee, 0x10, 0x57, 0x04, 0x97, 0x24, + 0x96, 0xa4, 0x0a, 0x59, 0x73, 0xb1, 0x15, 0x24, 0x16, 0x25, 0xe6, 0x16, 0x4b, 0x30, 0x2a, 0x30, + 0x6a, 0x70, 0x1b, 0xc9, 0xea, 0x61, 0x75, 0x95, 0x5e, 0x00, 0x58, 0x91, 0x13, 0xcb, 0x89, 0x7b, + 0xf2, 0x0c, 0x41, 0x50, 0x2d, 0x42, 0x21, 0x5c, 0xc2, 0x05, 0x45, 0xa9, 0x65, 0x99, 0xf9, 0xa5, + 0xc5, 0xf1, 0x49, 0x39, 0xf9, 0xc9, 0xd9, 0xf1, 0x20, 0x57, 0x49, 0x30, 0x81, 0x4d, 0x92, 0xd2, + 0x83, 0x38, 0x59, 0x0f, 0xe6, 0x64, 0xbd, 0x10, 0x98, 0x93, 0x9d, 0x38, 0x40, 0xc6, 0x4c, 0xb8, + 0x2f, 0xcf, 0x18, 0x24, 0x08, 0x33, 0xc0, 0x09, 0xa4, 0x1f, 0xa4, 0xc2, 0x8a, 0xa5, 0x63, 0x81, + 0x3c, 0x83, 0x93, 0xcb, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, + 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x69, 0xa5, + 0x67, 0x96, 0x64, 0x94, 0x26, 0xe9, 0x25, 0xe7, 0xe7, 0x82, 0xbd, 0xa7, 0x9b, 0x93, 0x98, 0x54, + 0x0c, 0x66, 0xe9, 0x57, 0x20, 0x02, 0xa0, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x6c, 0xb9, + 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x48, 0xff, 0xce, 0x91, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.PreviousBlockTime, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.PreviousBlockTime):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintGenesis(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x12 + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.PreviousBlockTime) + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PreviousBlockTime", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.PreviousBlockTime, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/kavamint/types/genesis_test.go b/x/kavamint/types/genesis_test.go new file mode 100644 index 00000000..e9c24915 --- /dev/null +++ b/x/kavamint/types/genesis_test.go @@ -0,0 +1,149 @@ +package types_test + +import ( + "encoding/json" + "testing" + + time "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" + + "github.com/kava-labs/kava/x/kavamint/types" +) + +func TestGenesis_Default(t *testing.T) { + defaultGenesis := types.DefaultGenesisState() + + require.NoError(t, defaultGenesis.Validate()) + + defaultParams := types.DefaultParams() + assert.Equal(t, defaultParams, defaultGenesis.Params) +} + +func TestGenesis_Validation(t *testing.T) { + testCases := []struct { + name string + gs *types.GenesisState + expectedErr string + }{ + { + "valid - default genesis is valid", + types.DefaultGenesisState(), + "", + }, + { + "valid - valid genesis", + types.NewGenesisState( + newValidParams(t), + time.Now(), + ), + "", + }, + { + "valid - no inflation", + types.NewGenesisState( + types.NewParams(sdk.ZeroDec(), sdk.ZeroDec()), + time.Now(), + ), + "", + }, + { + "valid - no time set", + types.NewGenesisState( + newValidParams(t), + time.Time{}, + ), + "", + }, + { + "invalid - community inflation param too big", + types.NewGenesisState( + types.NewParams(types.MaxMintingRate.Add(sdk.NewDecWithPrec(1, 18)), sdk.ZeroDec()), + time.Now(), + ), + "invalid rate: 100.000000000000000001", + }, + { + "invalid - staking reward inflation param too big", + types.NewGenesisState( + // inflation is larger than is allowed! + types.NewParams(sdk.ZeroDec(), types.MaxMintingRate.Add(sdk.NewDecWithPrec(1, 18))), + time.Now(), + ), + "invalid rate: 100.000000000000000001", + }, + { + "invalid - negative community inflation param", + types.NewGenesisState( + types.NewParams(sdk.OneDec().MulInt64(-1), sdk.ZeroDec()), + time.Now(), + ), + "invalid rate: -1.000000000000000000", + }, + { + "invalid - negative staking inflation param", + types.NewGenesisState( + types.NewParams(sdk.ZeroDec(), sdk.OneDec().MulInt64(-1)), + time.Now(), + ), + "invalid rate: -1.000000000000000000", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.gs.Validate() + + if tc.expectedErr == "" { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + }) + } +} + +func TestGenesis_JSONEncoding(t *testing.T) { + raw := `{ + "params": { + "community_pool_inflation": "0.000000000000000001", + "staking_rewards_apy": "0.000000000000000002" + }, + "previous_block_time": "2022-12-01T11:30:55.000000001Z" + }` + + var state types.GenesisState + err := json.Unmarshal([]byte(raw), &state) + require.NoError(t, err) + + assert.Equal(t, sdk.MustNewDecFromStr("0.000000000000000001"), state.Params.CommunityPoolInflation) + assert.Equal(t, sdk.MustNewDecFromStr("0.000000000000000002"), state.Params.StakingRewardsApy) + + prevBlockTime, err := time.Parse(time.RFC3339, "2022-12-01T11:30:55.000000001Z") + require.NoError(t, err) + + assert.Equal(t, prevBlockTime, state.PreviousBlockTime) +} + +func TestGenesis_YAMLEncoding(t *testing.T) { + expected := `params: + community_pool_inflation: "0.000000000000000001" + staking_rewards_apy: "0.000000000000000002" +previous_block_time: "2022-12-01T11:30:55.000000001Z" +` + prevBlockTime, err := time.Parse(time.RFC3339, "2022-12-01T11:30:55.000000001Z") + require.NoError(t, err) + + state := types.NewGenesisState( + types.NewParams(sdk.MustNewDecFromStr("0.000000000000000001"), sdk.MustNewDecFromStr("0.000000000000000002")), + prevBlockTime, + ) + + data, err := yaml.Marshal(state) + require.NoError(t, err) + + assert.Equal(t, expected, string(data)) +} diff --git a/x/kavamint/types/kavamint.pb.go b/x/kavamint/types/kavamint.pb.go new file mode 100644 index 00000000..b04237f5 --- /dev/null +++ b/x/kavamint/types/kavamint.pb.go @@ -0,0 +1,373 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: kava/kavamint/v1beta1/kavamint.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params wraps the governance parameters for the kavamint module +type Params struct { + // yearly inflation of total token supply minted to the community pool. + CommunityPoolInflation github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=community_pool_inflation,json=communityPoolInflation,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"community_pool_inflation"` + // yearly inflation of bonded tokens minted for staking rewards to validators. + StakingRewardsApy github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=staking_rewards_apy,json=stakingRewardsApy,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"staking_rewards_apy"` +} + +func (m *Params) Reset() { *m = Params{} } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_bdfe36d50f127f04, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func init() { + proto.RegisterType((*Params)(nil), "kava.kavamint.v1beta1.Params") +} + +func init() { + proto.RegisterFile("kava/kavamint/v1beta1/kavamint.proto", fileDescriptor_bdfe36d50f127f04) +} + +var fileDescriptor_bdfe36d50f127f04 = []byte{ + // 293 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc9, 0x4e, 0x2c, 0x4b, + 0xd4, 0x07, 0x11, 0xb9, 0x99, 0x79, 0x25, 0xfa, 0x65, 0x86, 0x49, 0xa9, 0x25, 0x89, 0x86, 0x70, + 0x01, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x51, 0x10, 0x5f, 0x0f, 0x2e, 0x08, 0x55, 0x25, + 0x25, 0x99, 0x9c, 0x5f, 0x9c, 0x9b, 0x5f, 0x1c, 0x0f, 0x56, 0xa4, 0x0f, 0xe1, 0x40, 0x74, 0x48, + 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x43, 0xc4, 0x41, 0x2c, 0x88, 0xa8, 0xd2, 0x0f, 0x46, 0x2e, 0xb6, + 0x80, 0xc4, 0xa2, 0xc4, 0xdc, 0x62, 0xa1, 0x32, 0x2e, 0x89, 0xe4, 0xfc, 0xdc, 0xdc, 0xd2, 0xbc, + 0xcc, 0x92, 0xca, 0xf8, 0x82, 0xfc, 0xfc, 0x9c, 0xf8, 0xcc, 0xbc, 0xb4, 0x9c, 0xc4, 0x92, 0xcc, + 0xfc, 0x3c, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0x9b, 0x13, 0xf7, 0xe4, 0x19, 0x6e, 0xdd, + 0x93, 0x57, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0x85, 0xda, 0x01, 0xa5, + 0x74, 0x8b, 0x53, 0xb2, 0xf5, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0xf5, 0x5c, 0x52, 0x93, 0x2f, 0x6d, + 0xd1, 0xe5, 0x82, 0x3a, 0xc1, 0x25, 0x35, 0x39, 0x48, 0x0c, 0x6e, 0x7a, 0x40, 0x7e, 0x7e, 0x8e, + 0x27, 0xcc, 0x6c, 0xa1, 0x1c, 0x2e, 0xe1, 0xe2, 0x92, 0xc4, 0xec, 0xcc, 0xbc, 0xf4, 0xf8, 0xa2, + 0xd4, 0xf2, 0xc4, 0xa2, 0x94, 0xe2, 0xf8, 0xc4, 0x82, 0x4a, 0x09, 0x26, 0x2a, 0x58, 0x29, 0x08, + 0x35, 0x38, 0x08, 0x62, 0xae, 0x63, 0x41, 0xa5, 0x15, 0x47, 0xc7, 0x02, 0x79, 0x86, 0x19, 0x0b, + 0xe4, 0x19, 0x9c, 0x5c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, + 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x0b, + 0xc9, 0x32, 0x50, 0x10, 0xeb, 0xe6, 0x24, 0x26, 0x15, 0x83, 0x59, 0xfa, 0x15, 0x88, 0x98, 0x01, + 0x5b, 0x9a, 0xc4, 0x06, 0x0e, 0x47, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x8a, 0xce, 0xb7, + 0xff, 0xb7, 0x01, 0x00, 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.StakingRewardsApy.Size() + i -= size + if _, err := m.StakingRewardsApy.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintKavamint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.CommunityPoolInflation.Size() + i -= size + if _, err := m.CommunityPoolInflation.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintKavamint(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintKavamint(dAtA []byte, offset int, v uint64) int { + offset -= sovKavamint(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.CommunityPoolInflation.Size() + n += 1 + l + sovKavamint(uint64(l)) + l = m.StakingRewardsApy.Size() + n += 1 + l + sovKavamint(uint64(l)) + return n +} + +func sovKavamint(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozKavamint(x uint64) (n int) { + return sovKavamint(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKavamint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CommunityPoolInflation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKavamint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthKavamint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthKavamint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.CommunityPoolInflation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field StakingRewardsApy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowKavamint + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthKavamint + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthKavamint + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.StakingRewardsApy.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipKavamint(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthKavamint + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipKavamint(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKavamint + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKavamint + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowKavamint + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthKavamint + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupKavamint + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthKavamint + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthKavamint = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowKavamint = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupKavamint = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/kavamint/types/keys.go b/x/kavamint/types/keys.go new file mode 100644 index 00000000..bc9a4517 --- /dev/null +++ b/x/kavamint/types/keys.go @@ -0,0 +1,18 @@ +package types + +const ( + // module name + ModuleName = "kavamint" + + // ModuleAccountName is the name of the module's account + ModuleAccountName = ModuleName + + // StoreKey is the default store key for mint + StoreKey = ModuleName + + // QuerierRoute is the querier route for the minting store. + QuerierRoute = StoreKey +) + +// PreviousBlockTimeKey is the store key for the previous block time +var PreviousBlockTimeKey = []byte{0x00} diff --git a/x/kavamint/types/params.go b/x/kavamint/types/params.go new file mode 100644 index 00000000..b546c557 --- /dev/null +++ b/x/kavamint/types/params.go @@ -0,0 +1,87 @@ +package types + +import ( + fmt "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +// Parameter keys and default values +var ( + KeyCommunityPoolInflation = []byte("CommunityPoolInflation") + KeyStakingRewardsApy = []byte("StakingRewardsApy") + + // default inflation values are zero + DefaultCommunityPoolInflation = sdk.ZeroDec() + DefaultStakingRewardsApy = sdk.ZeroDec() + + // MaxMintingRate returns the per second rate equivalent to 10,000% per year + MaxMintingRate = sdk.NewDec(100) +) + +// NewParams returns new Params with inflation rates set +func NewParams(communityPoolInflation sdk.Dec, stakingRewardsApy sdk.Dec) Params { + return Params{ + CommunityPoolInflation: communityPoolInflation, + StakingRewardsApy: stakingRewardsApy, + } +} + +// ParamKeyTable returns the key table for the kavamint module +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) +} + +// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs +func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyCommunityPoolInflation, &p.CommunityPoolInflation, validateCommunityPoolInflation), + paramtypes.NewParamSetPair(KeyStakingRewardsApy, &p.StakingRewardsApy, validateStakingRewardsApy), + } +} + +// DefaultParams returns default valid parameters for the kavamint module +func DefaultParams() Params { + return NewParams(DefaultCommunityPoolInflation, DefaultStakingRewardsApy) +} + +// Validate checks that the parameters have valid values. +func (p *Params) Validate() error { + if err := validateCommunityPoolInflation(p.CommunityPoolInflation); err != nil { + return err + } + return validateStakingRewardsApy(p.StakingRewardsApy) +} + +// String implements fmt.Stringer +func (p Params) String() string { + return fmt.Sprintf(`Params: + CommunityPoolInflation: %s + StakingRewardsApy: %s`, + p.CommunityPoolInflation, p.StakingRewardsApy) +} + +func validateCommunityPoolInflation(i interface{}) error { + rate, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + return validateRate(rate) +} + +func validateStakingRewardsApy(i interface{}) error { + rate, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + return validateRate(rate) +} + +// validateRate ensures rate is properly initialized (non-nil), not negative, and not greater than the max rate +func validateRate(rate sdk.Dec) error { + if rate.IsNil() || rate.IsNegative() || rate.GT(MaxMintingRate) { + return fmt.Errorf(fmt.Sprintf("invalid rate: %s", rate)) + } + return nil +} diff --git a/x/kavamint/types/params_test.go b/x/kavamint/types/params_test.go new file mode 100644 index 00000000..808a4c9b --- /dev/null +++ b/x/kavamint/types/params_test.go @@ -0,0 +1,297 @@ +package types_test + +import ( + "bytes" + "encoding/json" + fmt "fmt" + "reflect" + "testing" + + "github.com/kava-labs/kava/x/kavamint/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "sigs.k8s.io/yaml" +) + +const secondsPerYear = 31536000 + +func newValidParams(t *testing.T) types.Params { + // 50% + poolRate := sdk.MustNewDecFromStr("0.5") + + // 10% + stakingRate := sdk.MustNewDecFromStr("0.1") + + params := types.NewParams(poolRate, stakingRate) + require.NoError(t, params.Validate()) + + return params +} + +func TestParams_Default(t *testing.T) { + defaultParams := types.DefaultParams() + + require.NoError(t, defaultParams.Validate(), "default parameters must be valid") + + assert.Equal(t, types.DefaultCommunityPoolInflation, defaultParams.CommunityPoolInflation, "expected default pool inflation to match exported default value") + assert.Equal(t, defaultParams.CommunityPoolInflation, sdk.ZeroDec(), "expected default pool inflation to be zero") + + assert.Equal(t, types.DefaultStakingRewardsApy, defaultParams.StakingRewardsApy, "expected default staking inflation to match exported default value") + assert.Equal(t, defaultParams.CommunityPoolInflation, sdk.ZeroDec(), "expected default staking inflation to be zero") +} + +func TestParams_MaxInflationRate_ApproxRootDoesNotPanic(t *testing.T) { + require.Equal(t, sdk.NewDec(100), types.MaxMintingRate) // 10,000%, should never be a reason to exceed this value + maxYearlyRate := types.MaxMintingRate + + require.NotPanics(t, func() { + expectedMaxRate, err := maxYearlyRate.ApproxRoot(secondsPerYear) + require.NoError(t, err) + expectedMaxRate = expectedMaxRate.Sub(sdk.OneDec()) + }) +} + +func TestParams_MaxInflationRate_DoesNotOverflow(t *testing.T) { + maxRate := types.MaxMintingRate // use the max minting rate + totalSupply := sdk.NewDec(1e14) // 100 trillion starting supply + years := uint64(25) // calculate over 50 years + + perSecondMaxRate, err := maxRate.ApproxRoot(secondsPerYear) + require.NoError(t, err) + perSecondMaxRate = perSecondMaxRate.Sub(sdk.OneDec()) + + var finalSupply sdk.Int + + require.NotPanics(t, func() { + compoundedRate := perSecondMaxRate.Power(years * secondsPerYear) + finalSupply = totalSupply.Mul(compoundedRate).RoundInt() + + }) + + require.Less(t, finalSupply.BigInt().BitLen(), 256) +} + +func TestParams_ParamSetPairs_CommunityPoolInflation(t *testing.T) { + assert.Equal(t, []byte("CommunityPoolInflation"), types.KeyCommunityPoolInflation) + defaultParams := types.DefaultParams() + + var paramSetPair *paramstypes.ParamSetPair + for _, pair := range defaultParams.ParamSetPairs() { + if bytes.Equal(pair.Key, types.KeyCommunityPoolInflation) { + paramSetPair = &pair + break + } + } + require.NotNil(t, paramSetPair) + + pairs, ok := paramSetPair.Value.(*sdk.Dec) + require.True(t, ok) + assert.Equal(t, pairs, &defaultParams.CommunityPoolInflation) + + assert.Nil(t, paramSetPair.ValidatorFn(*pairs)) + assert.EqualError(t, paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}") +} + +func TestParams_ParamSetPairs_StakingRewardsApy(t *testing.T) { + assert.Equal(t, []byte("StakingRewardsApy"), types.KeyStakingRewardsApy) + defaultParams := types.DefaultParams() + + var paramSetPair *paramstypes.ParamSetPair + for _, pair := range defaultParams.ParamSetPairs() { + if bytes.Equal(pair.Key, types.KeyStakingRewardsApy) { + paramSetPair = &pair + break + } + } + require.NotNil(t, paramSetPair) + + pairs, ok := paramSetPair.Value.(*sdk.Dec) + require.True(t, ok) + assert.Equal(t, pairs, &defaultParams.StakingRewardsApy) + + assert.Nil(t, paramSetPair.ValidatorFn(*pairs)) + assert.EqualError(t, paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}") +} + +func TestParams_Validation(t *testing.T) { + testCases := []struct { + name string + key []byte + testFn func(params *types.Params) + expectedErr string + }{ + { + name: "nil community pool inflation", + key: types.KeyCommunityPoolInflation, + testFn: func(params *types.Params) { + params.CommunityPoolInflation = sdk.Dec{} + }, + expectedErr: "invalid rate: ", + }, + { + name: "negative community pool inflation", + key: types.KeyCommunityPoolInflation, + testFn: func(params *types.Params) { + params.CommunityPoolInflation = sdk.MustNewDecFromStr("-0.000000000011111111") + }, + expectedErr: "invalid rate: -0.000000000011111111", + }, + { + name: "0 community pool inflation", + key: types.KeyCommunityPoolInflation, + testFn: func(params *types.Params) { + params.CommunityPoolInflation = sdk.ZeroDec() + }, + expectedErr: "", // ok + }, + { + name: "community pool inflation 1e-18 less than max rate", + key: types.KeyCommunityPoolInflation, + testFn: func(params *types.Params) { + params.CommunityPoolInflation = types.MaxMintingRate.Sub(sdk.NewDecWithPrec(1, 18)) + }, + expectedErr: "", // ok + }, + { + name: "community pool inflation equal to max rate", + key: types.KeyCommunityPoolInflation, + testFn: func(params *types.Params) { + params.CommunityPoolInflation = types.MaxMintingRate + }, + expectedErr: "", // ok + }, + { + name: "community pool inflation 1e-18 greater than max rate", + key: types.KeyCommunityPoolInflation, + testFn: func(params *types.Params) { + params.CommunityPoolInflation = types.MaxMintingRate.Add(sdk.NewDecWithPrec(1, 18)) + }, + expectedErr: "invalid rate: 100.000000000000000001", + }, + { + name: "nil staking inflation", + key: types.KeyStakingRewardsApy, + testFn: func(params *types.Params) { + params.StakingRewardsApy = sdk.Dec{} + }, + expectedErr: "invalid rate: ", + }, + { + name: "negative staking inflation", + key: types.KeyStakingRewardsApy, + testFn: func(params *types.Params) { + params.StakingRewardsApy = sdk.MustNewDecFromStr("-0.000000002222222222") + }, + expectedErr: "invalid rate: -0.000000002222222222", + }, + { + name: "0 staking inflation", + key: types.KeyStakingRewardsApy, + testFn: func(params *types.Params) { + params.StakingRewardsApy = sdk.ZeroDec() + }, + expectedErr: "", // ok + }, + { + name: "staking inflation 1e-18 less than max rate", + key: types.KeyStakingRewardsApy, + testFn: func(params *types.Params) { + params.StakingRewardsApy = types.MaxMintingRate.Sub(sdk.NewDecWithPrec(1, 18)) + }, + expectedErr: "", // ok + }, + { + name: "staking inflation equal to max rate", + key: types.KeyStakingRewardsApy, + testFn: func(params *types.Params) { + params.StakingRewardsApy = types.MaxMintingRate + }, + expectedErr: "", // ok + }, + { + name: "staking inflation 1e-18 greater than max rate", + key: types.KeyStakingRewardsApy, + testFn: func(params *types.Params) { + params.StakingRewardsApy = types.MaxMintingRate.Add(sdk.NewDecWithPrec(1, 18)) + }, + expectedErr: "invalid rate: 100.000000000000000001", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + params := types.DefaultParams() + tc.testFn(¶ms) + + err := params.Validate() + + if tc.expectedErr == "" { + assert.Nil(t, err) + } else { + assert.EqualError(t, err, tc.expectedErr) + } + + var paramSetPair *paramstypes.ParamSetPair + for _, pair := range params.ParamSetPairs() { + if bytes.Equal(pair.Key, tc.key) { + paramSetPair = &pair + break + } + } + require.NotNil(t, paramSetPair) + value := reflect.ValueOf(paramSetPair.Value).Elem().Interface() + + // assert validation error is same as param set validation + assert.Equal(t, err, paramSetPair.ValidatorFn(value)) + }) + } +} + +func TestParams_String(t *testing.T) { + params := newValidParams(t) + + output := params.String() + assert.Contains(t, output, fmt.Sprintf("CommunityPoolInflation: %s", params.CommunityPoolInflation.String())) + assert.Contains(t, output, fmt.Sprintf("StakingRewardsApy: %s", params.StakingRewardsApy.String())) +} + +func TestParams_UnmarshalJSON(t *testing.T) { + params := newValidParams(t) + + poolRateData, err := json.Marshal(params.CommunityPoolInflation) + require.NoError(t, err) + + stakingRateData, err := json.Marshal(params.StakingRewardsApy) + require.NoError(t, err) + + data := fmt.Sprintf(`{ + "community_pool_inflation": %s, + "staking_rewards_apy": %s +}`, string(poolRateData), string(stakingRateData)) + + var parsedParams types.Params + err = json.Unmarshal([]byte(data), &parsedParams) + require.NoError(t, err) + + assert.Equal(t, params.CommunityPoolInflation, parsedParams.CommunityPoolInflation) + assert.Equal(t, params.StakingRewardsApy, parsedParams.StakingRewardsApy) +} + +func TestParams_MarshalYAML(t *testing.T) { + p := newValidParams(t) + + data, err := yaml.Marshal(p) + require.NoError(t, err) + + var params map[string]interface{} + err = yaml.Unmarshal(data, ¶ms) + require.NoError(t, err) + + _, ok := params["community_pool_inflation"] + require.True(t, ok) + _, ok = params["staking_rewards_apy"] + require.True(t, ok) +} diff --git a/x/kavamint/types/query.pb.go b/x/kavamint/types/query.pb.go new file mode 100644 index 00000000..9427de8a --- /dev/null +++ b/x/kavamint/types/query.pb.go @@ -0,0 +1,870 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: kava/kavamint/v1beta1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryParamsRequest defines the request type for querying x/kavamint parameters. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_3a66171519c59de7, []int{0} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse defines the response type for querying x/kavamint parameters. +type QueryParamsResponse struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_3a66171519c59de7, []int{1} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// QueryInflationRequest is the request type for the Query/Inflation RPC method. +type QueryInflationRequest struct { +} + +func (m *QueryInflationRequest) Reset() { *m = QueryInflationRequest{} } +func (m *QueryInflationRequest) String() string { return proto.CompactTextString(m) } +func (*QueryInflationRequest) ProtoMessage() {} +func (*QueryInflationRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_3a66171519c59de7, []int{2} +} +func (m *QueryInflationRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryInflationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryInflationRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryInflationRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryInflationRequest.Merge(m, src) +} +func (m *QueryInflationRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryInflationRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryInflationRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryInflationRequest proto.InternalMessageInfo + +// QueryInflationResponse is the response type for the Query/Inflation RPC method. +type QueryInflationResponse struct { + // inflation is the current minting inflation value. + // example "0.990000000000000000" - 99% inflation + Inflation github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=inflation,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"inflation"` +} + +func (m *QueryInflationResponse) Reset() { *m = QueryInflationResponse{} } +func (m *QueryInflationResponse) String() string { return proto.CompactTextString(m) } +func (*QueryInflationResponse) ProtoMessage() {} +func (*QueryInflationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_3a66171519c59de7, []int{3} +} +func (m *QueryInflationResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryInflationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryInflationResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryInflationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryInflationResponse.Merge(m, src) +} +func (m *QueryInflationResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryInflationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryInflationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryInflationResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*QueryParamsRequest)(nil), "kava.kavamint.v1beta1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "kava.kavamint.v1beta1.QueryParamsResponse") + proto.RegisterType((*QueryInflationRequest)(nil), "kava.kavamint.v1beta1.QueryInflationRequest") + proto.RegisterType((*QueryInflationResponse)(nil), "kava.kavamint.v1beta1.QueryInflationResponse") +} + +func init() { proto.RegisterFile("kava/kavamint/v1beta1/query.proto", fileDescriptor_3a66171519c59de7) } + +var fileDescriptor_3a66171519c59de7 = []byte{ + // 397 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcc, 0x4e, 0x2c, 0x4b, + 0xd4, 0x07, 0x11, 0xb9, 0x99, 0x79, 0x25, 0xfa, 0x65, 0x86, 0x49, 0xa9, 0x25, 0x89, 0x86, 0xfa, + 0x85, 0xa5, 0xa9, 0x45, 0x95, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xa2, 0x20, 0x59, 0x3d, + 0x98, 0x12, 0x3d, 0xa8, 0x12, 0x29, 0xc9, 0xe4, 0xfc, 0xe2, 0xdc, 0xfc, 0xe2, 0x78, 0xb0, 0x22, + 0x7d, 0x08, 0x07, 0xa2, 0x43, 0x4a, 0x24, 0x3d, 0x3f, 0x3d, 0x1f, 0x22, 0x0e, 0x62, 0x41, 0x45, + 0x65, 0xd2, 0xf3, 0xf3, 0xd3, 0x73, 0x52, 0xf5, 0x13, 0x0b, 0x32, 0xf5, 0x13, 0xf3, 0xf2, 0xf2, + 0x4b, 0x12, 0x4b, 0x32, 0xf3, 0xf3, 0x60, 0x7a, 0x54, 0xb0, 0x3b, 0x04, 0x6e, 0x2d, 0x58, 0x95, + 0x92, 0x08, 0x97, 0x50, 0x20, 0xc8, 0x69, 0x01, 0x89, 0x45, 0x89, 0xb9, 0xc5, 0x41, 0xa9, 0x85, + 0xa5, 0xa9, 0xc5, 0x25, 0x4a, 0x41, 0x5c, 0xc2, 0x28, 0xa2, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, + 0x42, 0xd6, 0x5c, 0x6c, 0x05, 0x60, 0x11, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x59, 0x3d, + 0xac, 0x3e, 0xd1, 0x83, 0x68, 0x73, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x08, 0xaa, 0x45, 0x49, + 0x9c, 0x4b, 0x14, 0x6c, 0xa6, 0x67, 0x5e, 0x5a, 0x0e, 0xd8, 0xa1, 0x30, 0xcb, 0x4a, 0xb8, 0xc4, + 0xd0, 0x25, 0xa0, 0xf6, 0x45, 0x71, 0x71, 0x66, 0xc2, 0x04, 0xc1, 0x56, 0x72, 0x3a, 0xd9, 0x80, + 0xcc, 0xbc, 0x75, 0x4f, 0x5e, 0x2d, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x17, + 0x1a, 0x54, 0x50, 0x4a, 0xb7, 0x38, 0x25, 0x5b, 0xbf, 0xa4, 0xb2, 0x20, 0xb5, 0x58, 0xcf, 0x25, + 0x35, 0xf9, 0xd2, 0x16, 0x5d, 0x2e, 0x68, 0x48, 0xba, 0xa4, 0x26, 0x07, 0x21, 0x8c, 0x33, 0x5a, + 0xca, 0xc4, 0xc5, 0x0a, 0xb6, 0x56, 0xa8, 0x8d, 0x91, 0x8b, 0x0d, 0xe2, 0x62, 0x21, 0x4d, 0x1c, + 0x1e, 0xc2, 0x0c, 0x22, 0x29, 0x2d, 0x62, 0x94, 0x42, 0xfc, 0xa1, 0xa4, 0xda, 0x74, 0xf9, 0xc9, + 0x64, 0x26, 0x79, 0x21, 0x59, 0x7d, 0xec, 0x71, 0x02, 0x09, 0x21, 0xa1, 0x49, 0x8c, 0x5c, 0x9c, + 0xf0, 0x40, 0x10, 0xd2, 0xc1, 0x67, 0x01, 0x7a, 0x20, 0x4a, 0xe9, 0x12, 0xa9, 0x1a, 0xea, 0x22, + 0x0d, 0xb0, 0x8b, 0x94, 0x84, 0x14, 0x70, 0xb8, 0x08, 0x1e, 0x4e, 0x4e, 0x2e, 0x27, 0x1e, 0xc9, + 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, + 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0xa5, 0x85, 0x14, 0x05, 0x20, 0x03, 0x74, 0x73, 0x12, + 0x93, 0x8a, 0x21, 0xe6, 0x55, 0x20, 0x4c, 0x04, 0x47, 0x45, 0x12, 0x1b, 0x38, 0xb5, 0x19, 0x03, + 0x02, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x9b, 0x02, 0x7d, 0x1e, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params queries the parameters of x/kavamint module. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // Inflation queries x/kavamint for the overall cumulative inflation rate of KAVA. + Inflation(ctx context.Context, in *QueryInflationRequest, opts ...grpc.CallOption) (*QueryInflationResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/kava.kavamint.v1beta1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Inflation(ctx context.Context, in *QueryInflationRequest, opts ...grpc.CallOption) (*QueryInflationResponse, error) { + out := new(QueryInflationResponse) + err := c.cc.Invoke(ctx, "/kava.kavamint.v1beta1.Query/Inflation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params queries the parameters of x/kavamint module. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // Inflation queries x/kavamint for the overall cumulative inflation rate of KAVA. + Inflation(context.Context, *QueryInflationRequest) (*QueryInflationResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} +func (*UnimplementedQueryServer) Inflation(ctx context.Context, req *QueryInflationRequest) (*QueryInflationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Inflation not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kava.kavamint.v1beta1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Inflation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryInflationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Inflation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/kava.kavamint.v1beta1.Query/Inflation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Inflation(ctx, req.(*QueryInflationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "kava.kavamint.v1beta1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + { + MethodName: "Inflation", + Handler: _Query_Inflation_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "kava/kavamint/v1beta1/query.proto", +} + +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *QueryInflationRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryInflationRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryInflationRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryInflationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryInflationResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryInflationResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Inflation.Size() + i -= size + if _, err := m.Inflation.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryInflationRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryInflationResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Inflation.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryInflationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryInflationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryInflationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryInflationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryInflationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryInflationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Inflation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Inflation.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/kavamint/types/query.pb.gw.go b/x/kavamint/types/query.pb.gw.go new file mode 100644 index 00000000..28e01ce0 --- /dev/null +++ b/x/kavamint/types/query.pb.gw.go @@ -0,0 +1,218 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: kava/kavamint/v1beta1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_Inflation_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryInflationRequest + var metadata runtime.ServerMetadata + + msg, err := client.Inflation(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Inflation_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryInflationRequest + var metadata runtime.ServerMetadata + + msg, err := server.Inflation(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Inflation_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Inflation_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Inflation_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Inflation_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Inflation_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Inflation_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "kavamint", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Inflation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "kavamint", "v1beta1", "inflation"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_Inflation_0 = runtime.ForwardResponseMessage +)