From b95ade93bd4e81df541f4eb67f987ab128c50296 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Mon, 1 Apr 2019 13:28:36 -0400 Subject: [PATCH] Merge PR #4011: Mint/Inflation Querier, LCD and CLI * A few godoc updates * More minor tweaks and reformatting * Implement initial minting querier * Implement stringer interface for minting params * Minor cleanup * Add minting CLI commands * Implement inflation query command * Implement annual provisions query and CLI command * Update x/mint/client/module_client.go Co-Authored-By: alexanderbez * Update x/mint/client/module_client.go Co-Authored-By: alexanderbez * Update x/mint/client/module_client.go Co-Authored-By: alexanderbez * Update x/mint/querier.go Co-Authored-By: alexanderbez * Add minting REST client routes/handlers * Fix build issues * Implement querier unit tests * Update gaiacli docs * Implement LCD tests * Update Swagger docs * Add pending log entry * add examples Signed-off-by: Karoly Albert Szabo * revert adding examples Signed-off-by: Karoly Albert Szabo --- ...inting-module-querier-and-CLI-REST-clients | 1 + client/lcd/lcd_test.go | 28 ++++++ client/lcd/swagger-ui/swagger.yaml | 93 +++++++++++++++++++ client/lcd/test_helpers.go | 2 + cmd/gaia/app/app.go | 3 +- cmd/gaia/cmd/gaiacli/main.go | 16 +++- docs/gaia/gaiacli.md | 20 ++++ x/mint/client/cli/query.go | 89 ++++++++++++++++++ x/mint/client/module_client.go | 47 ++++++++++ x/mint/client/rest/query.go | 71 ++++++++++++++ x/mint/client/rest/rest.go | 12 +++ x/mint/keeper.go | 22 +++-- x/mint/minter.go | 30 +++--- x/mint/params.go | 14 +++ x/mint/querier.go | 68 ++++++++++++++ x/mint/querier_test.go | 74 +++++++++++++++ x/mint/test_common.go | 75 +++++++++++++++ 17 files changed, 635 insertions(+), 30 deletions(-) create mode 100644 .pending/features/gaia/3886-Implement-minting-module-querier-and-CLI-REST-clients create mode 100644 x/mint/client/cli/query.go create mode 100644 x/mint/client/module_client.go create mode 100644 x/mint/client/rest/query.go create mode 100644 x/mint/client/rest/rest.go create mode 100644 x/mint/querier.go create mode 100644 x/mint/querier_test.go create mode 100644 x/mint/test_common.go diff --git a/.pending/features/gaia/3886-Implement-minting-module-querier-and-CLI-REST-clients b/.pending/features/gaia/3886-Implement-minting-module-querier-and-CLI-REST-clients new file mode 100644 index 0000000000..480f542e7a --- /dev/null +++ b/.pending/features/gaia/3886-Implement-minting-module-querier-and-CLI-REST-clients @@ -0,0 +1 @@ +#3886 Implement minting module querier and CLI/REST clients. diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index d42e0e7e70..0ead2f3636 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -11,6 +11,8 @@ import ( "testing" "time" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/stretchr/testify/require" "github.com/cosmos/cosmos-sdk/client" @@ -1005,3 +1007,29 @@ func TestDistributionFlow(t *testing.T) { resultTx = doWithdrawDelegatorAllRewards(t, port, seed, name1, pw, addr, fees) require.Equal(t, uint32(0), resultTx.Code) } + +func TestMintingQueries(t *testing.T) { + kb, err := keys.NewKeyBaseFromDir(InitClientHome(t, "")) + require.NoError(t, err) + addr, _ := CreateAddr(t, name1, pw, kb) + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}, true) + defer cleanup() + + res, body := Request(t, port, "GET", "/minting/parameters", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var params mint.Params + require.NoError(t, cdc.UnmarshalJSON([]byte(body), ¶ms)) + + res, body = Request(t, port, "GET", "/minting/inflation", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var inflation sdk.Dec + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &inflation)) + + res, body = Request(t, port, "GET", "/minting/annual-provisions", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var annualProvisions sdk.Dec + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &annualProvisions)) +} diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 0ec9ce34a7..6df405f54f 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -122,6 +122,7 @@ paths: description: Block height required: true type: number + x-example: 1 responses: 200: description: The block at a specific height @@ -167,6 +168,7 @@ paths: description: Block height required: true type: number + x-example: 1 responses: 200: description: The validator set at a specific block height @@ -198,6 +200,7 @@ paths: description: Tx hash required: true type: string + x-example: 88D6B85EAB87D43CDF50F39C22FC2237A37FEDC4CE723200AD0AF48CBEDBC317 responses: 200: description: Tx with the provided hash @@ -219,14 +222,17 @@ paths: type: string description: "transaction tags such as 'action=submit-proposal' and 'proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc' which results in the following endpoint: 'GET /txs?action=submit-proposal&proposer=cosmos1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc'" required: true + x-example: 'TODO' - in: query name: page description: Page number type: integer + x-example: 1 - in: query name: limit description: Maximum number of items per page type: integer + x-example: 1 responses: 200: description: All txs matching the provided tags @@ -313,6 +319,7 @@ paths: description: Account address in bech32 format required: true type: string + x-example: cosmos16gdxm24ht2mxtpz9cma6tr6a6d47x63hlq4pxt responses: 200: description: Account balances @@ -339,6 +346,7 @@ paths: description: Account address in bech32 format required: true type: string + x-example: cosmos16gdxm24ht2mxtpz9cma6tr6a6d47x63hlq4pxt - in: body name: account description: The sender and tx information @@ -374,6 +382,7 @@ paths: description: Account address required: true type: string + x-example: cosmos16gdxm24ht2mxtpz9cma6tr6a6d47x63hlq4pxt responses: 200: description: Account information on the blockchain @@ -408,6 +417,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf get: summary: Get all delegations from a delegator tags: @@ -466,11 +476,13 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Query the current delegation between a delegator and a validator tags: @@ -493,6 +505,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf get: summary: Get all unbonding delegations from a delegator tags: @@ -552,11 +565,13 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Query all unbonding delegations between a delegator and a validator tags: @@ -613,6 +628,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf post: summary: Submit a redelegation parameters: @@ -655,6 +671,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf get: summary: Query all validators that a delegator is bonded to tags: @@ -679,11 +696,13 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf - in: path name: validatorAddr description: Bech32 ValAddress of Delegator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Query a validator that a delegator is bonded to tags: @@ -706,6 +725,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf get: summary: Get all staking txs (i.e msgs) from a delegator tags: @@ -748,6 +768,7 @@ paths: description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Query the information from a single validator tags: @@ -770,6 +791,7 @@ paths: description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Get all delegations from a validator tags: @@ -794,6 +816,7 @@ paths: description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Get all unbonding delegations from a validator tags: @@ -881,6 +904,7 @@ paths: name: validatorPubKey required: true in: path + x-example: cosmosvalconspub1zcjduepq7mft6gfls57a0a42d7uhx656cckhfvtrlmw744jv4q0mvlv0dypskehfk8 responses: 200: description: OK @@ -906,11 +930,13 @@ paths: description: Page number type: integer required: true + x-example: 1 - in: query name: limit description: Maximum number of items per page type: integer required: true + x-example: 5 responses: 200: description: OK @@ -940,6 +966,7 @@ paths: name: validatorAddr required: true in: path + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys - description: "" name: UnjailBody in: body @@ -1076,6 +1103,7 @@ paths: name: proposalId required: true in: path + x-example: '1' responses: 200: description: OK @@ -1098,6 +1126,7 @@ paths: name: proposalId required: true in: path + x-example: '1' responses: 200: description: OK @@ -1120,6 +1149,7 @@ paths: name: proposalId required: true in: path + x-example: '1' responses: 200: description: OK @@ -1146,6 +1176,7 @@ paths: name: proposalId required: true in: path + x-example: '1' - description: "" name: post_deposit_body in: body @@ -1186,11 +1217,13 @@ paths: name: proposalId required: true in: path + x-example: '1' - type: string description: Bech32 depositor address name: depositor required: true in: path + x-example: cosmos1xl6453f6q6dv5770c9ue6hspdc0vxfuqtudkhz responses: 200: description: OK @@ -1216,6 +1249,7 @@ paths: name: proposalId required: true in: path + x-example: '1' responses: 200: description: OK @@ -1242,6 +1276,7 @@ paths: name: proposalId required: true in: path + x-example: '1' - description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"` name: post_vote_body in: body @@ -1281,11 +1316,13 @@ paths: name: proposalId required: true in: path + x-example: '1' - type: string description: Bech32 voter address name: voter required: true in: path + x-example: cosmos1qwl879nx9t6kef4supyazayf7vjhennyjqwjgr responses: 200: description: OK @@ -1311,6 +1348,7 @@ paths: name: proposalId required: true in: path + x-example: '1' responses: 200: description: OK @@ -1404,6 +1442,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf get: summary: Get the total rewards balance from all delegations description: Get the sum of all the rewards earned by delegations by a single delegator @@ -1456,11 +1495,13 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Query a delegation reward description: Query a single delegation reward by a delegator @@ -1513,6 +1554,7 @@ paths: description: Bech32 AccAddress of Delegator required: true type: string + x-example: cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf get: summary: Get the rewards withdrawal address description: Get the delegations' rewards withdrawal address. This is the address in which the user will receive the reward funds @@ -1565,6 +1607,7 @@ paths: description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Validator distribution information description: Query the distribution information of a single validator @@ -1588,6 +1631,7 @@ paths: description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Fee distribution outstanding rewards of a single validator tags: @@ -1610,6 +1654,7 @@ paths: description: Bech32 OperatorAddress of validator required: true type: string + x-example: cosmosvaloper1qwl879nx9t6kef4supyazayf7vjhennyh568ys get: summary: Commission and self-delegation rewards of a single validator description: Query the commission and self-delegation rewards of validator. @@ -1691,6 +1736,54 @@ paths: type: string 500: description: Internal Server Error + /minting/parameters: + get: + summary: Minting module parameters + produces: + - application/json + responses: + 200: + description: OK + schema: + properties: + mint_denom: + type: string + inflation_rate_change: + type: string + inflation_max: + type: string + inflation_min: + type: string + goal_bonded: + type: string + blocks_per_year: + type: integer + 500: + description: Internal Server Error + /minting/inflation: + get: + summary: Current minting inflation value + produces: + - application/json + responses: + 200: + description: OK + schema: + type: string + 500: + description: Internal Server Error + /minting/annual-provisions: + get: + summary: Current minting annual provisions value + produces: + - application/json + responses: + 200: + description: OK + schema: + type: string + 500: + description: Internal Server Error definitions: CheckTxResult: type: object diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index 5521dd9b8f..856efaf95f 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -42,6 +42,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" govrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" gcutils "github.com/cosmos/cosmos-sdk/x/gov/client/utils" + mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest" "github.com/cosmos/cosmos-sdk/x/slashing" slashingrest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" "github.com/cosmos/cosmos-sdk/x/staking" @@ -411,6 +412,7 @@ func registerRoutes(rs *RestServer) { stakingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) slashingrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) govrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) + mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) } // Request makes a test LCD test request. It returns a response object and a diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index d0925a1585..262115649f 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -179,7 +179,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest, AddRoute(distr.QuerierRoute, distr.NewQuerier(app.distrKeeper)). AddRoute(gov.QuerierRoute, gov.NewQuerier(app.govKeeper)). AddRoute(slashing.QuerierRoute, slashing.NewQuerier(app.slashingKeeper, app.cdc)). - AddRoute(staking.QuerierRoute, staking.NewQuerier(app.stakingKeeper, app.cdc)) + AddRoute(staking.QuerierRoute, staking.NewQuerier(app.stakingKeeper, app.cdc)). + AddRoute(mint.QuerierRoute, mint.NewQuerier(app.mintKeeper)) // initialize BaseApp app.MountStores(app.keyMain, app.keyAccount, app.keyStaking, app.keyMint, app.keyDistr, diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 10a1692196..b440db8f94 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -6,11 +6,13 @@ import ( "os" "path" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/rakyll/statik/fs" "github.com/spf13/cobra" "github.com/spf13/viper" - amino "github.com/tendermint/go-amino" + "github.com/tendermint/go-amino" "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" @@ -28,6 +30,7 @@ import ( dist "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" gv "github.com/cosmos/cosmos-sdk/x/gov" gov "github.com/cosmos/cosmos-sdk/x/gov/client/rest" + mintrest "github.com/cosmos/cosmos-sdk/x/mint/client/rest" sl "github.com/cosmos/cosmos-sdk/x/slashing" slashing "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" st "github.com/cosmos/cosmos-sdk/x/staking" @@ -39,8 +42,9 @@ import ( distcmd "github.com/cosmos/cosmos-sdk/x/distribution" distClient "github.com/cosmos/cosmos-sdk/x/distribution/client" govClient "github.com/cosmos/cosmos-sdk/x/gov/client" - slashingClient "github.com/cosmos/cosmos-sdk/x/slashing/client" - stakingClient "github.com/cosmos/cosmos-sdk/x/staking/client" + mintclient "github.com/cosmos/cosmos-sdk/x/mint/client" + slashingclient "github.com/cosmos/cosmos-sdk/x/slashing/client" + stakingclient "github.com/cosmos/cosmos-sdk/x/staking/client" _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" ) @@ -68,8 +72,9 @@ func main() { mc := []sdk.ModuleClients{ govClient.NewModuleClient(gv.StoreKey, cdc), distClient.NewModuleClient(distcmd.StoreKey, cdc), - stakingClient.NewModuleClient(st.StoreKey, cdc), - slashingClient.NewModuleClient(sl.StoreKey, cdc), + stakingclient.NewModuleClient(st.StoreKey, cdc), + mintclient.NewModuleClient(mint.StoreKey, cdc), + slashingclient.NewModuleClient(sl.StoreKey, cdc), crisisclient.NewModuleClient(sl.StoreKey, cdc), } @@ -171,6 +176,7 @@ func registerRoutes(rs *lcd.RestServer) { staking.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) slashing.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) gov.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) + mintrest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) } func registerSwaggerUI(rs *lcd.RestServer) { diff --git a/docs/gaia/gaiacli.md b/docs/gaia/gaiacli.md index 132c4aeed4..d3cf2a2339 100644 --- a/docs/gaia/gaiacli.md +++ b/docs/gaia/gaiacli.md @@ -327,6 +327,26 @@ You can get the current slashing parameters via: gaiacli query slashing params ``` +### Minting + +You can query for the minting/inflation parameters via: + +```bash +gaiacli query minting params +``` + +To query for the current inflation value: + +```bash +gaiacli query minting inflation +``` + +To query for the current annual provisions value: + +```bash +gaiacli query minting annual-provisions +``` + ### Staking #### Set up a Validator diff --git a/x/mint/client/cli/query.go b/x/mint/client/cli/query.go new file mode 100644 index 0000000000..4980240730 --- /dev/null +++ b/x/mint/client/cli/query.go @@ -0,0 +1,89 @@ +package cli + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/spf13/cobra" +) + +// GetCmdQueryParams implements a command to return the current minting +// parameters. +func GetCmdQueryParams(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "params", + Short: "Query the current minting parameters", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryParameters) + res, err := cliCtx.QueryWithData(route, nil) + if err != nil { + return err + } + + var params mint.Params + if err := cdc.UnmarshalJSON(res, ¶ms); err != nil { + return err + } + + return cliCtx.PrintOutput(params) + }, + } +} + +// GetCmdQueryInflation implements a command to return the current minting +// inflation value. +func GetCmdQueryInflation(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "inflation", + Short: "Query the current minting inflation value", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryInflation) + res, err := cliCtx.QueryWithData(route, nil) + if err != nil { + return err + } + + var inflation sdk.Dec + if err := cdc.UnmarshalJSON(res, &inflation); err != nil { + return err + } + + return cliCtx.PrintOutput(inflation) + }, + } +} + +// GetCmdQueryAnnualProvisions implements a command to return the current minting +// annual provisions value. +func GetCmdQueryAnnualProvisions(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "annual-provisions", + Short: "Query the current minting annual provisions value", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + + route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryAnnualProvisions) + res, err := cliCtx.QueryWithData(route, nil) + if err != nil { + return err + } + + var inflation sdk.Dec + if err := cdc.UnmarshalJSON(res, &inflation); err != nil { + return err + } + + return cliCtx.PrintOutput(inflation) + }, + } +} diff --git a/x/mint/client/module_client.go b/x/mint/client/module_client.go new file mode 100644 index 0000000000..96f6c00fdf --- /dev/null +++ b/x/mint/client/module_client.go @@ -0,0 +1,47 @@ +package clientpackage + +import ( + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/mint/client/cli" + "github.com/spf13/cobra" + "github.com/tendermint/go-amino" +) + +// ModuleClient exports all CLI client functionality from the minting module. +type ModuleClient struct { + storeKey string + cdc *amino.Codec +} + +func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { + return ModuleClient{storeKey, cdc} +} + +// GetQueryCmd returns the cli query commands for the minting module. +func (mc ModuleClient) GetQueryCmd() *cobra.Command { + mintingQueryCmd := &cobra.Command{ + Use: mint.ModuleName, + Short: "Querying commands for the minting module", + } + + mintingQueryCmd.AddCommand( + client.GetCommands( + cli.GetCmdQueryParams(mc.cdc), + cli.GetCmdQueryInflation(mc.cdc), + cli.GetCmdQueryAnnualProvisions(mc.cdc), + )..., + ) + + return mintingQueryCmd +} + +// GetTxCmd returns the transaction commands for the minting module. +func (mc ModuleClient) GetTxCmd() *cobra.Command { + mintTxCmd := &cobra.Command{ + Use: mint.ModuleName, + Short: "Minting transaction subcommands", + } + + return mintTxCmd +} diff --git a/x/mint/client/rest/query.go b/x/mint/client/rest/query.go new file mode 100644 index 0000000000..9bc436a6d5 --- /dev/null +++ b/x/mint/client/rest/query.go @@ -0,0 +1,71 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/gorilla/mux" +) + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { + r.HandleFunc( + "/minting/parameters", + queryParamsHandlerFn(cdc, cliCtx), + ).Methods("GET") + + r.HandleFunc( + "/minting/inflation", + queryInflationHandlerFn(cdc, cliCtx), + ).Methods("GET") + + r.HandleFunc( + "/minting/annual-provisions", + queryAnnualProvisionsHandlerFn(cdc, cliCtx), + ).Methods("GET") +} + +func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryParameters) + + res, err := cliCtx.QueryWithData(route, nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func queryInflationHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryInflation) + + res, err := cliCtx.QueryWithData(route, nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func queryAnnualProvisionsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + route := fmt.Sprintf("custom/%s/%s", mint.QuerierRoute, mint.QueryAnnualProvisions) + + res, err := cliCtx.QueryWithData(route, nil) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} diff --git a/x/mint/client/rest/rest.go b/x/mint/client/rest/rest.go new file mode 100644 index 0000000000..cedfa72c74 --- /dev/null +++ b/x/mint/client/rest/rest.go @@ -0,0 +1,12 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/gorilla/mux" +) + +// RegisterRoutes registers minting module REST handlers on the provided router. +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) { + registerQueryRoutes(cliCtx, r, cdc) +} diff --git a/x/mint/keeper.go b/x/mint/keeper.go index e532fa7447..42a9f19fba 100644 --- a/x/mint/keeper.go +++ b/x/mint/keeper.go @@ -6,6 +6,20 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) +const ( + // ModuleName is the name of the module + ModuleName = "minting" + + // default paramspace for params keeper + DefaultParamspace = "mint" + + // StoreKey is the default store key for mint + StoreKey = "mint" + + // QuerierRoute is the querier route for the minting store. + QuerierRoute = StoreKey +) + // keeper of the staking store type Keeper struct { storeKey sdk.StoreKey @@ -45,14 +59,6 @@ func ParamKeyTable() params.KeyTable { ) } -const ( - // default paramspace for params keeper - DefaultParamspace = "mint" - - // StoreKey is the default store key for mint - StoreKey = "mint" -) - //______________________________________________________________________ // get the minter diff --git a/x/mint/minter.go b/x/mint/minter.go index a28e8f4476..eff30785fa 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -6,22 +6,22 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Minter represents the minting state +// Minter represents the minting state. type Minter struct { Inflation sdk.Dec `json:"inflation"` // current annual inflation rate AnnualProvisions sdk.Dec `json:"annual_provisions"` // current annual expected provisions } -// Create a new minter object +// NewMinter returns a new Minter object with the given inflation and annual +// provisions values. func NewMinter(inflation, annualProvisions sdk.Dec) Minter { - return Minter{ Inflation: inflation, AnnualProvisions: annualProvisions, } } -// minter object for a new chain +// InitialMinter returns an initial Minter object with a given inflation value. func InitialMinter(inflation sdk.Dec) Minter { return NewMinter( inflation, @@ -29,8 +29,8 @@ func InitialMinter(inflation sdk.Dec) Minter { ) } -// default initial minter object for a new chain -// which uses an inflation rate of 13% +// DefaultInitialMinter returns a default initial Minter object for a new chain +// which uses an inflation rate of 13%. func DefaultInitialMinter() Minter { return InitialMinter( sdk.NewDecWithPrec(13, 2), @@ -45,10 +45,8 @@ func validateMinter(minter Minter) error { return nil } -// get the new inflation rate for the next hour -func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( - inflation sdk.Dec) { - +// NextInflationRate returns the new inflation rate for the next hour. +func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) sdk.Dec { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive or negative) depending on // the distance from the desired ratio (67%). The maximum rate change possible is @@ -62,7 +60,7 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( inflationRateChange := inflationRateChangePerYear.Quo(sdk.NewDec(int64(params.BlocksPerYear))) // adjust the new annual inflation for this next cycle - inflation = m.Inflation.Add(inflationRateChange) // note inflationRateChange may be negative + inflation := m.Inflation.Add(inflationRateChange) // note inflationRateChange may be negative if inflation.GT(params.InflationMax) { inflation = params.InflationMax } @@ -73,14 +71,14 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( return inflation } -// calculate the annual provisions based on current total supply and inflation rate -func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Int) ( - provisions sdk.Dec) { - +// NextAnnualProvisions returns the annual provisions based on current total +// supply and inflation rate. +func (m Minter) NextAnnualProvisions(_ Params, totalSupply sdk.Int) sdk.Dec { return m.Inflation.MulInt(totalSupply) } -// get the provisions for a block based on the annual provisions rate +// BlockProvision returns the provisions for a block based on the annual +// provisions rate. func (m Minter) BlockProvision(params Params) sdk.Coin { provisionAmt := m.AnnualProvisions.QuoInt(sdk.NewInt(int64(params.BlocksPerYear))) return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt()) diff --git a/x/mint/params.go b/x/mint/params.go index 38366851eb..f1bd7e37d9 100644 --- a/x/mint/params.go +++ b/x/mint/params.go @@ -56,3 +56,17 @@ func validateParams(params Params) error { } return nil } + +func (p Params) String() string { + return fmt.Sprintf(`Minting Params: + Mint Denom: %s + Inflation Rate Change: %s + Inflation Max: %s + Inflation Min: %s + Goal Bonded: %s + Blocks Per Year: %d +`, + p.MintDenom, p.InflationRateChange, p.InflationMax, + p.InflationMin, p.GoalBonded, p.BlocksPerYear, + ) +} diff --git a/x/mint/querier.go b/x/mint/querier.go new file mode 100644 index 0000000000..21d35ce15c --- /dev/null +++ b/x/mint/querier.go @@ -0,0 +1,68 @@ +package mint + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// Query endpoints supported by the minting querier +const ( + QueryParameters = "parameters" + QueryInflation = "inflation" + QueryAnnualProvisions = "annual_provisions" +) + +// NewQuerier returns a minting Querier handler. +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, _ abci.RequestQuery) ([]byte, sdk.Error) { + switch path[0] { + case QueryParameters: + return queryParams(ctx, k) + + case QueryInflation: + return queryInflation(ctx, k) + + case QueryAnnualProvisions: + return queryAnnualProvisions(ctx, k) + + default: + return nil, sdk.ErrUnknownRequest(fmt.Sprintf("unknown minting query endpoint: %s", path[0])) + } + } +} + +func queryParams(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { + params := k.GetParams(ctx) + + res, err := codec.MarshalJSONIndent(k.cdc, params) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to marshal JSON", err.Error())) + } + + return res, nil +} + +func queryInflation(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { + minter := k.GetMinter(ctx) + + res, err := codec.MarshalJSONIndent(k.cdc, minter.Inflation) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to marshal JSON", err.Error())) + } + + return res, nil +} + +func queryAnnualProvisions(ctx sdk.Context, k Keeper) ([]byte, sdk.Error) { + minter := k.GetMinter(ctx) + + res, err := codec.MarshalJSONIndent(k.cdc, minter.AnnualProvisions) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("failed to marshal JSON", err.Error())) + } + + return res, nil +} diff --git a/x/mint/querier_test.go b/x/mint/querier_test.go new file mode 100644 index 0000000000..e9f3c8d6c6 --- /dev/null +++ b/x/mint/querier_test.go @@ -0,0 +1,74 @@ +package mint + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" +) + +func TestNewQuerier(t *testing.T) { + input := newTestInput(t) + querier := NewQuerier(input.mintKeeper) + + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + _, err := querier(input.ctx, []string{QueryParameters}, query) + require.NoError(t, err) + + _, err = querier(input.ctx, []string{QueryInflation}, query) + require.NoError(t, err) + + _, err = querier(input.ctx, []string{QueryAnnualProvisions}, query) + require.NoError(t, err) + + _, err = querier(input.ctx, []string{"foo"}, query) + require.Error(t, err) +} + +func TestQueryParams(t *testing.T) { + input := newTestInput(t) + + var params Params + + res, sdkErr := queryParams(input.ctx, input.mintKeeper) + require.NoError(t, sdkErr) + + err := input.cdc.UnmarshalJSON(res, ¶ms) + require.NoError(t, err) + + require.Equal(t, input.mintKeeper.GetParams(input.ctx), params) +} + +func TestQueryInflation(t *testing.T) { + input := newTestInput(t) + + var inflation sdk.Dec + + res, sdkErr := queryInflation(input.ctx, input.mintKeeper) + require.NoError(t, sdkErr) + + err := input.cdc.UnmarshalJSON(res, &inflation) + require.NoError(t, err) + + require.Equal(t, input.mintKeeper.GetMinter(input.ctx).Inflation, inflation) +} + +func TestQueryAnnualProvisions(t *testing.T) { + input := newTestInput(t) + + var annualProvisions sdk.Dec + + res, sdkErr := queryAnnualProvisions(input.ctx, input.mintKeeper) + require.NoError(t, sdkErr) + + err := input.cdc.UnmarshalJSON(res, &annualProvisions) + require.NoError(t, err) + + require.Equal(t, input.mintKeeper.GetMinter(input.ctx).AnnualProvisions, annualProvisions) +} diff --git a/x/mint/test_common.go b/x/mint/test_common.go new file mode 100644 index 0000000000..f699acda23 --- /dev/null +++ b/x/mint/test_common.go @@ -0,0 +1,75 @@ +package mint + +import ( + "os" + "testing" + "time" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/staking" + dbm "github.com/tendermint/tendermint/libs/db" +) + +type testInput struct { + ctx sdk.Context + cdc *codec.Codec + mintKeeper Keeper +} + +func createTestCodec() *codec.Codec { + cdc := codec.New() + codec.RegisterCrypto(cdc) + return cdc +} + +func newTestInput(t *testing.T) testInput { + cdc := createTestCodec() + db := dbm.NewMemDB() + + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyStaking := sdk.NewKVStoreKey(staking.StoreKey) + tkeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keyMint := sdk.NewKVStoreKey(StoreKey) + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeTransient, nil) + ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyMint, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + + paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams) + feeCollectionKeeper := auth.NewFeeCollectionKeeper(cdc, keyFeeCollection) + accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) + bankKeeper := bank.NewBaseKeeper(accountKeeper, paramsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) + stakingKeeper := staking.NewKeeper( + cdc, keyStaking, tkeyStaking, bankKeeper, paramsKeeper.Subspace(staking.DefaultParamspace), staking.DefaultCodespace, + ) + mintKeeper := NewKeeper( + cdc, keyMint, paramsKeeper.Subspace(DefaultParamspace), &stakingKeeper, feeCollectionKeeper, + ) + + ctx := sdk.NewContext(ms, abci.Header{Time: time.Unix(0, 0)}, false, log.NewTMLogger(os.Stdout)) + + mintKeeper.SetParams(ctx, DefaultParams()) + mintKeeper.SetMinter(ctx, DefaultInitialMinter()) + + return testInput{ctx, cdc, mintKeeper} +}