diff --git a/PENDING.md b/PENDING.md index 73e2f4ce3f79..f375f6c962a7 100644 --- a/PENDING.md +++ b/PENDING.md @@ -6,6 +6,7 @@ BREAKING CHANGES * [\#3284](https://github.com/cosmos/cosmos-sdk/issues/3284) Rename the `name` field to `from` in the `base_req` body. * [\#3485](https://github.com/cosmos/cosmos-sdk/pull/3485) Error responses are now JSON objects. + * [\#3477][distribution] endpoint changed "all_delegation_rewards" -> "delegator_total_rewards" * Gaia CLI (`gaiacli`) - [#3399](https://github.com/cosmos/cosmos-sdk/pull/3399) Add `gaiad validate-genesis` command to facilitate checking of genesis files @@ -18,6 +19,7 @@ BREAKING CHANGES * SDK * [\#3487](https://github.com/cosmos/cosmos-sdk/pull/3487) Move HTTP/REST utilities out of client/utils into a new dedicated client/rest package. + * [\#3490](https://github.com/cosmos/cosmos-sdk/issues/3490) ReadRESTReq() returns bool to avoid callers to write error responses twice. * Tendermint @@ -25,6 +27,7 @@ BREAKING CHANGES FEATURES * Gaia REST API + * [\#2358](https://github.com/cosmos/cosmos-sdk/issues/2358) Add distribution module REST interface * Gaia CLI (`gaiacli`) * [\#3429](https://github.com/cosmos/cosmos-sdk/issues/3429) Support querying @@ -37,6 +40,7 @@ FEATURES * SDK * \#3270 [x/staking] limit number of ongoing unbonding delegations /redelegations per pair/trio + * [\#3477][distribution] new query endpoint "delegator_validators" * Tendermint @@ -53,6 +57,7 @@ IMPROVEMENTS (auto gas) to work with generate only. * Gaia CLI (`gaiacli`) + * [\#3476](https://github.com/cosmos/cosmos-sdk/issues/3476) New `withdraw-all-rewards` command to withdraw all delegations rewards for delegators. * Gaia * [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index c531a00404ac..591984abd2cb 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -26,6 +26,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" "github.com/cosmos/cosmos-sdk/x/bank" + dclcommon "github.com/cosmos/cosmos-sdk/x/distribution/client/common" + distrrest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" @@ -914,3 +916,99 @@ func TestSlashingGetParams(t *testing.T) { err := cdc.UnmarshalJSON([]byte(body), ¶ms) require.NoError(t, err) } + +func TestDistributionGetParams(t *testing.T) { + cleanup, _, _, port := InitializeTestLCD(t, 1, []sdk.AccAddress{}) + defer cleanup() + + res, body := Request(t, port, "GET", "/distribution/parameters", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &dclcommon.PrettyParams{})) +} + +func TestDistributionFlow(t *testing.T) { + addr, seed := CreateAddr(t, name1, pw, GetKeyBase(t)) + //addr2, seed2 = CreateAddr(t, name2, pw, GetKeyBase(t)) + cleanup, _, valAddrs, port := InitializeTestLCD(t, 1, []sdk.AccAddress{addr}) + defer cleanup() + + valAddr := valAddrs[0] + operAddr := sdk.AccAddress(valAddr) + + var rewards sdk.DecCoins + res, body := Request(t, port, "GET", fmt.Sprintf("/distribution/outstanding_rewards"), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + require.Equal(t, sdk.DecCoins(nil), rewards) + + var valDistInfo distrrest.ValidatorDistInfo + res, body = Request(t, port, "GET", "/distribution/validators/"+valAddr.String(), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &valDistInfo)) + require.Equal(t, valDistInfo.OperatorAddress.String(), sdk.AccAddress(valAddr).String()) + require.Equal(t, valDistInfo.ValidatorCommission, sdk.DecCoins(nil)) + require.Equal(t, valDistInfo.SelfBondRewards, sdk.DecCoins(nil)) + + // Delegate some coins + resultTx := doDelegate(t, port, name1, pw, addr, valAddr, 60, fees) + tests.WaitForHeight(resultTx.Height+1, port) + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // send some coins + _, resultTx = doTransfer(t, port, seed, name1, memo, pw, addr, fees) + tests.WaitForHeight(resultTx.Height+5, port) + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) + + // Query outstanding rewards changed + oustandingRewards := mustParseDecCoins("9.80stake") + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/outstanding_rewards"), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + require.Equal(t, oustandingRewards, rewards) + + // Query validator distribution info + res, body = Request(t, port, "GET", "/distribution/validators/"+valAddr.String(), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + valRewards := mustParseDecCoins("6.125stake") + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &valDistInfo)) + require.Equal(t, valRewards, valDistInfo.SelfBondRewards) + + // Query validator's rewards + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/validators/%s/rewards", valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + require.Equal(t, valRewards, rewards) + + // Query self-delegation + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards/%s", operAddr, valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + require.Equal(t, valRewards, rewards) + + // Query delegation + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards/%s", addr, valAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + require.Equal(t, mustParseDecCoins("3.675stake"), rewards) + + // Query delegator's rewards total + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/rewards", operAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &rewards)) + require.Equal(t, valRewards, rewards) + + // Query delegator's withdrawal address + var withdrawAddr string + res, body = Request(t, port, "GET", fmt.Sprintf("/distribution/delegators/%s/withdraw_address", operAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + require.NoError(t, cdc.UnmarshalJSON([]byte(body), &withdrawAddr)) + require.Equal(t, operAddr.String(), withdrawAddr) + + // Withdraw delegator's rewards + resultTx = doWithdrawDelegatorAllRewards(t, port, seed, name1, pw, addr, fees) + require.Equal(t, uint32(0), resultTx.CheckTx.Code) + require.Equal(t, uint32(0), resultTx.DeliverTx.Code) +} diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index 363345917aac..38e9c1864399 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -18,7 +18,7 @@ tags: - name: ICS23 description: Slashing module APIs - name: ICS24 - description: WIP - Fee distribution module APIs + description: Fee distribution module APIs - name: version description: Query app version schemes: @@ -1846,9 +1846,9 @@ paths: type: string 500: description: Internal Server Error - /distribution/pool: + /distribution/outstanding_rewards: get: - summary: Fee distribution pool + summary: Fee distribution outstanding rewards tags: - ICS24 produces: @@ -1857,7 +1857,9 @@ paths: 200: description: OK schema: - $ref: "#/definitions/FeePool" + type: array + items: + $ref: "#/definitions/Coin" 500: description: Internal Server Error definitions: @@ -2198,7 +2200,7 @@ definitions: power: type: string example: "1000" - accum: + proposer_priority: type: string example: "1000" TextProposal: @@ -2367,36 +2369,12 @@ definitions: type: string shares_dst: type: string - FeePool: - type: object - properties: - community_pool: - type: array - items: - $ref: "#/definitions/Coin" - val_accum: - $ref: "#/definitions/TotalAccum" - val_pool: - type: array - items: - $ref: "#/definitions/Coin" - TotalAccum: - type: object - properties: - update_height: - type: integer - accum: - type: string ValidatorDistInfo: type: object properties: operator_addr: $ref: "#/definitions/ValidatorAddress" - fee_pool_withdrawal_height: - type: integer - del_accum: - $ref: "#/definitions/TotalAccum" - del_pool: + self_bond_rewards: type: array items: $ref: "#/definitions/Coin" diff --git a/client/lcd/test_helpers.go b/client/lcd/test_helpers.go index b420215eb245..efef932a6025 100644 --- a/client/lcd/test_helpers.go +++ b/client/lcd/test_helpers.go @@ -60,6 +60,8 @@ import ( authRest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bankRest "github.com/cosmos/cosmos-sdk/x/bank/client/rest" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrRest "github.com/cosmos/cosmos-sdk/x/distribution/client/rest" govRest "github.com/cosmos/cosmos-sdk/x/gov/client/rest" slashingRest "github.com/cosmos/cosmos-sdk/x/slashing/client/rest" stakingRest "github.com/cosmos/cosmos-sdk/x/staking/client/rest" @@ -294,6 +296,11 @@ func InitializeTestLCD( genesisState.StakingData.Pool.NotBondedTokens = genesisState.StakingData.Pool.NotBondedTokens.Add(sdk.NewInt(100)) } + inflationMin := sdk.MustNewDecFromStr("10000.0") + genesisState.MintData.Minter.Inflation = inflationMin + genesisState.MintData.Params.InflationMax = sdk.MustNewDecFromStr("15000.0") + genesisState.MintData.Params.InflationMin = inflationMin + appState, err := codec.MarshalJSONIndent(cdc, genesisState) require.NoError(t, err) genDoc.AppState = appState @@ -390,6 +397,7 @@ func registerRoutes(rs *RestServer) { tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) authRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, auth.StoreKey) bankRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + distrRest.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distr.StoreKey) 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) @@ -1382,3 +1390,37 @@ func doUnjail(t *testing.T, port, seed, name, password string, type unjailReq struct { BaseReq rest.BaseReq `json:"base_req"` } + +// ICS24 - fee distribution + +// POST /distribution/delegators/{delgatorAddr}/rewards Withdraw delegator rewards +func doWithdrawDelegatorAllRewards(t *testing.T, port, seed, name, password string, + delegatorAddr sdk.AccAddress, fees sdk.Coins) (resultTx ctypes.ResultBroadcastTxCommit) { + // get the account to get the sequence + acc := getAccount(t, port, delegatorAddr) + accnum := acc.GetAccountNumber() + sequence := acc.GetSequence() + chainID := viper.GetString(client.FlagChainID) + baseReq := rest.NewBaseReq(name, password, "", chainID, "", "", accnum, sequence, fees, nil, false, false) + + wr := struct { + BaseReq rest.BaseReq `json:"base_req"` + }{BaseReq: baseReq} + + req := cdc.MustMarshalJSON(wr) + res, body := Request(t, port, "POST", fmt.Sprintf("/distribution/delegators/%s/rewards", delegatorAddr), req) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + var results ctypes.ResultBroadcastTxCommit + cdc.MustUnmarshalJSON([]byte(body), &results) + + return results +} + +func mustParseDecCoins(dcstring string) sdk.DecCoins { + dcoins, err := sdk.ParseDecCoins(dcstring) + if err != nil { + panic(err) + } + return dcoins +} diff --git a/client/rest/types.go b/client/rest/types.go index 113dc0650d02..cb409627db99 100644 --- a/client/rest/types.go +++ b/client/rest/types.go @@ -98,7 +98,7 @@ func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool { /* ReadRESTReq is a simple convenience wrapper that reads the body and -unmarshals to the req interface. +unmarshals to the req interface. Returns false if errors occurred. Usage: type SomeReq struct { @@ -107,20 +107,22 @@ unmarshals to the req interface. } req := new(SomeReq) - err := ReadRESTReq(w, r, cdc, req) + if ok := ReadRESTReq(w, r, cdc, req); !ok { + return + } */ -func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) error { +func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) bool { body, err := ioutil.ReadAll(r.Body) if err != nil { WriteErrorResponse(w, http.StatusBadRequest, err.Error()) - return err + return false } err = cdc.UnmarshalJSON(body, req) if err != nil { WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err)) - return err + return false } - return nil + return true } diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index 3042c35d60eb..3065ee64c0c9 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -25,7 +25,7 @@ import ( at "github.com/cosmos/cosmos-sdk/x/auth" auth "github.com/cosmos/cosmos-sdk/x/auth/client/rest" bank "github.com/cosmos/cosmos-sdk/x/bank/client/rest" - dist "github.com/cosmos/cosmos-sdk/x/distribution" + 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" sl "github.com/cosmos/cosmos-sdk/x/slashing" @@ -35,6 +35,7 @@ import ( authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + 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" @@ -65,7 +66,7 @@ func main() { // TODO: Make the lcd command take a list of ModuleClient mc := []sdk.ModuleClients{ govClient.NewModuleClient(gv.StoreKey, cdc), - distClient.NewModuleClient(dist.StoreKey, cdc), + distClient.NewModuleClient(distcmd.StoreKey, cdc), stakingClient.NewModuleClient(st.StoreKey, cdc), slashingClient.NewModuleClient(sl.StoreKey, cdc), } @@ -161,6 +162,7 @@ func registerRoutes(rs *lcd.RestServer) { tx.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc) auth.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, at.StoreKey) bank.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, rs.KeyBase) + dist.RegisterRoutes(rs.CliCtx, rs.Mux, rs.Cdc, distcmd.StoreKey) 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) diff --git a/x/auth/client/rest/sign.go b/x/auth/client/rest/sign.go index 50ec660552fa..67572eefe080 100644 --- a/x/auth/client/rest/sign.go +++ b/x/auth/client/rest/sign.go @@ -26,8 +26,7 @@ func SignTxRequestHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Ha return func(w http.ResponseWriter, r *http.Request) { var m SignBody - if err := rest.ReadRESTReq(w, r, cdc, &m); err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + if !rest.ReadRESTReq(w, r, cdc, &m) { return } diff --git a/x/bank/client/rest/sendtx.go b/x/bank/client/rest/sendtx.go index 70aae05fccc0..c335ebcba7c2 100644 --- a/x/bank/client/rest/sendtx.go +++ b/x/bank/client/rest/sendtx.go @@ -45,8 +45,7 @@ func SendRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CLIC } var req sendReq - err = rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { + if !rest.ReadRESTReq(w, r, cdc, &req) { return } diff --git a/x/distribution/alias.go b/x/distribution/alias.go index d050eddad568..85b93fc1745b 100644 --- a/x/distribution/alias.go +++ b/x/distribution/alias.go @@ -23,9 +23,10 @@ type ( FeeCollectionKeeper = types.FeeCollectionKeeper // querier param types - QueryValidatorCommissionParams = keeper.QueryValidatorCommissionParams - QueryValidatorSlashesParams = keeper.QueryValidatorSlashesParams - QueryDelegationRewardsParams = keeper.QueryDelegationRewardsParams + QueryValidatorCommissionParams = keeper.QueryValidatorCommissionParams + QueryValidatorSlashesParams = keeper.QueryValidatorSlashesParams + QueryDelegationRewardsParams = keeper.QueryDelegationRewardsParams + QueryDelegatorWithdrawAddrParams = keeper.QueryDelegatorWithdrawAddrParams ) const ( @@ -49,12 +50,14 @@ var ( NewMsgWithdrawDelegatorReward = types.NewMsgWithdrawDelegatorReward NewMsgWithdrawValidatorCommission = types.NewMsgWithdrawValidatorCommission - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier - NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams - NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams - NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams - DefaultParamspace = keeper.DefaultParamspace + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + NewQueryValidatorCommissionParams = keeper.NewQueryValidatorCommissionParams + NewQueryValidatorSlashesParams = keeper.NewQueryValidatorSlashesParams + NewQueryDelegationRewardsParams = keeper.NewQueryDelegationRewardsParams + NewQueryDelegatorParams = keeper.NewQueryDelegatorParams + NewQueryDelegatorWithdrawAddrParams = keeper.NewQueryDelegatorWithdrawAddrParams + DefaultParamspace = keeper.DefaultParamspace RegisterCodec = types.RegisterCodec DefaultGenesisState = types.DefaultGenesisState diff --git a/x/distribution/client/cli/query.go b/x/distribution/client/cli/query.go index 4f4eb93908df..2a23a56b9879 100644 --- a/x/distribution/client/cli/query.go +++ b/x/distribution/client/cli/query.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/distribution/client/common" "github.com/cosmos/cosmos-sdk/x/distribution/types" ) @@ -21,34 +22,10 @@ func GetCmdQueryParams(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Query distribution params", RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - - route := fmt.Sprintf("custom/%s/params/community_tax", queryRoute) - retCommunityTax, err := cliCtx.QueryWithData(route, []byte{}) - if err != nil { - return err - } - - route = fmt.Sprintf("custom/%s/params/base_proposer_reward", queryRoute) - retBaseProposerReward, err := cliCtx.QueryWithData(route, []byte{}) + params, err := common.QueryParams(cliCtx, queryRoute) if err != nil { return err } - - route = fmt.Sprintf("custom/%s/params/bonus_proposer_reward", queryRoute) - retBonusProposerReward, err := cliCtx.QueryWithData(route, []byte{}) - if err != nil { - return err - } - - route = fmt.Sprintf("custom/%s/params/withdraw_addr_enabled", queryRoute) - retWithdrawAddrEnabled, err := cliCtx.QueryWithData(route, []byte{}) - if err != nil { - return err - } - - params := NewPrettyParams(retCommunityTax, retBaseProposerReward, - retBonusProposerReward, retWithdrawAddrEnabled) - return cliCtx.PrintOutput(params) }, } @@ -90,13 +67,7 @@ func GetCmdQueryValidatorCommission(queryRoute string, cdc *codec.Codec) *cobra. return err } - bz, err := cdc.MarshalJSON(distr.NewQueryValidatorCommissionParams(validatorAddr)) - if err != nil { - return err - } - - route := fmt.Sprintf("custom/%s/validator_commission", queryRoute) - res, err := cliCtx.QueryWithData(route, bz) + res, err := common.QueryValidatorCommission(cliCtx, cdc, queryRoute, validatorAddr) if err != nil { return err } @@ -159,42 +130,20 @@ func GetCmdQueryDelegatorRewards(queryRoute string, cdc *codec.Codec) *cobra.Com RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - delegatorAddr, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - return err - } - - var ( - route string - params distr.QueryDelegationRewardsParams - result sdk.DecCoins - ) - - if len(args) == 1 { - // query for all rewards - params = distr.NewQueryDelegationRewardsParams(delegatorAddr, nil) - route = fmt.Sprintf("custom/%s/all_delegation_rewards", queryRoute) + var resp []byte + var err error + if len(args) == 2 { + // query for rewards from a particular delegation + resp, err = common.QueryDelegationRewards(cliCtx, cdc, queryRoute, args[0], args[1]) } else { - // query for rewards from a particular validator - validatorAddr, err := sdk.ValAddressFromBech32(args[1]) - if err != nil { - return err - } - - params = distr.NewQueryDelegationRewardsParams(delegatorAddr, validatorAddr) - route = fmt.Sprintf("custom/%s/delegation_rewards", queryRoute) + // query for delegator total rewards + resp, err = common.QueryDelegatorTotalRewards(cliCtx, cdc, queryRoute, args[0]) } - - bz, err := cdc.MarshalJSON(params) - if err != nil { - return err - } - - resp, err := cliCtx.QueryWithData(route, bz) if err != nil { return err } + var result sdk.DecCoins cdc.MustUnmarshalJSON(resp, &result) return cliCtx.PrintOutput(result) }, diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go index 9a761d8de6c1..21a3bb4c3674 100644 --- a/x/distribution/client/cli/tx.go +++ b/x/distribution/client/cli/tx.go @@ -16,6 +16,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/cosmos/cosmos-sdk/x/distribution/client/common" "github.com/cosmos/cosmos-sdk/x/distribution/types" ) @@ -89,7 +90,39 @@ func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { return cmd } -// GetCmdDelegate implements the delegate command. +// command to withdraw all rewards +func GetCmdWithdrawAllRewards(cdc *codec.Codec, queryRoute string) *cobra.Command { + cmd := &cobra.Command{ + Use: "withdraw-all-rewards [delegator-addr]", + Short: "withdraw all delegations rewards for a delegator", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(cdc) + + delAddr := cliCtx.GetFromAddress() + msgs, err := common.WithdrawAllDelegatorRewards(cliCtx, cdc, queryRoute, delAddr) + if err != nil { + return err + } + + if cliCtx.GenerateOnly { + return utils.PrintUnsignedStdTx(os.Stdout, txBldr, cliCtx, msgs, false) + } + + // build and sign the transaction, then broadcast to Tendermint + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, msgs) + }, + } + cmd.Flags().String(flagOnlyFromValidator, "", "only withdraw from this validator address (in bech)") + cmd.Flags().Bool(flagIsValidator, false, "also withdraw validator's commission") + return cmd +} + +// command to replace a delegator's withdrawal address func GetCmdSetWithdrawAddr(cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "set-withdraw-addr [withdraw-addr]", diff --git a/x/distribution/client/common/common.go b/x/distribution/client/common/common.go new file mode 100644 index 000000000000..de78a9bf2328 --- /dev/null +++ b/x/distribution/client/common/common.go @@ -0,0 +1,145 @@ +package common + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" +) + +// QueryParams actually queries distribution params. +func QueryParams(cliCtx context.CLIContext, queryRoute string) (PrettyParams, error) { + route := fmt.Sprintf("custom/%s/params/community_tax", queryRoute) + + retCommunityTax, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return PrettyParams{}, err + } + + route = fmt.Sprintf("custom/%s/params/base_proposer_reward", queryRoute) + retBaseProposerReward, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return PrettyParams{}, err + } + + route = fmt.Sprintf("custom/%s/params/bonus_proposer_reward", queryRoute) + retBonusProposerReward, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return PrettyParams{}, err + } + + route = fmt.Sprintf("custom/%s/params/withdraw_addr_enabled", queryRoute) + retWithdrawAddrEnabled, err := cliCtx.QueryWithData(route, []byte{}) + if err != nil { + return PrettyParams{}, err + } + + return NewPrettyParams(retCommunityTax, retBaseProposerReward, + retBonusProposerReward, retWithdrawAddrEnabled), nil +} + +// QueryDelegatorTotalRewards queries delegator total rewards. +func QueryDelegatorTotalRewards(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute, delAddr string) ([]byte, error) { + + delegatorAddr, err := sdk.AccAddressFromBech32(delAddr) + if err != nil { + return nil, err + } + + return cliCtx.QueryWithData( + fmt.Sprintf("custom/%s/delegator_total_rewards", queryRoute), + cdc.MustMarshalJSON(distr.NewQueryDelegatorParams(delegatorAddr)), + ) +} + +// QueryDelegationRewards queries a delegation rewards. +func QueryDelegationRewards(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute, delAddr, valAddr string) ([]byte, error) { + + delegatorAddr, err := sdk.AccAddressFromBech32(delAddr) + if err != nil { + return nil, err + } + validatorAddr, err := sdk.ValAddressFromBech32(valAddr) + if err != nil { + return nil, err + } + + return cliCtx.QueryWithData( + fmt.Sprintf("custom/%s/delegation_rewards", queryRoute), + cdc.MustMarshalJSON(distr.NewQueryDelegationRewardsParams(delegatorAddr, validatorAddr)), + ) +} + +// QueryDelegatorValidators returns delegator's list of validators +// it submitted delegations to. +func QueryDelegatorValidators(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string, delegatorAddr sdk.AccAddress) ([]byte, error) { + + return cliCtx.QueryWithData( + fmt.Sprintf("custom/%s/delegator_validators", queryRoute), + cdc.MustMarshalJSON(distr.NewQueryDelegatorParams(delegatorAddr)), + ) +} + +// QueryValidatorCommission returns a validator's commission. +func QueryValidatorCommission(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string, validatorAddr sdk.ValAddress) ([]byte, error) { + + return cliCtx.QueryWithData( + fmt.Sprintf("custom/%s/validator_commission", queryRoute), + cdc.MustMarshalJSON(distr.NewQueryValidatorCommissionParams(validatorAddr)), + ) +} + +// WithdrawAllDelegatorRewards builds a multi-message slice to be used +// to withdraw all delegations rewards for the given delegator. +func WithdrawAllDelegatorRewards(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string, delegatorAddr sdk.AccAddress) ([]sdk.Msg, error) { + + // retrieve the comprehensive list of all validators which the + // delegator had submitted delegations to + bz, err := QueryDelegatorValidators(cliCtx, cdc, queryRoute, delegatorAddr) + if err != nil { + return nil, err + } + + var validators []sdk.ValAddress + if err := cdc.UnmarshalJSON(bz, &validators); err != nil { + return nil, err + } + + // build multi-message transaction + var msgs []sdk.Msg + for _, valAddr := range validators { + msg := distr.NewMsgWithdrawDelegatorReward(delegatorAddr, valAddr) + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + msgs = append(msgs, msg) + } + + return msgs, nil +} + +// WithdrawValidatorRewardsAndCommission builds a two-message message slice to be +// used to withdraw both validation's commission and self-delegation reward. +func WithdrawValidatorRewardsAndCommission(validatorAddr sdk.ValAddress) ([]sdk.Msg, error) { + + commissionMsg := distr.NewMsgWithdrawValidatorCommission(validatorAddr) + if err := commissionMsg.ValidateBasic(); err != nil { + return nil, err + } + + // build and validate MsgWithdrawDelegatorReward + rewardMsg := distr.NewMsgWithdrawDelegatorReward( + sdk.AccAddress(validatorAddr.Bytes()), validatorAddr) + if err := rewardMsg.ValidateBasic(); err != nil { + return nil, err + } + + return []sdk.Msg{commissionMsg, rewardMsg}, nil +} diff --git a/x/distribution/client/common/common_test.go b/x/distribution/client/common/common_test.go new file mode 100644 index 000000000000..622326232a05 --- /dev/null +++ b/x/distribution/client/common/common_test.go @@ -0,0 +1,35 @@ +package common + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/stretchr/testify/require" +) + +func TestQueryDelegationRewardsAddrValidation(t *testing.T) { + cdc := codec.New() + ctx := context.NewCLIContext().WithCodec(cdc) + type args struct { + delAddr string + valAddr string + } + tests := []struct { + name string + args args + want []byte + wantErr bool + }{ + {"invalid delegator address", args{"invalid", ""}, nil, true}, + {"empty delegator address", args{"", ""}, nil, true}, + {"invalid validator address", args{"cosmos1zxcsu7l5qxs53lvp0fqgd09a9r2g6kqrk2cdpa", "invalid"}, nil, true}, + {"empty validator address", args{"cosmos1zxcsu7l5qxs53lvp0fqgd09a9r2g6kqrk2cdpa", ""}, nil, true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := QueryDelegationRewards(ctx, cdc, "", tt.args.delAddr, tt.args.valAddr) + require.True(t, err != nil, tt.wantErr) + }) + } +} diff --git a/x/distribution/client/cli/util.go b/x/distribution/client/common/pretty_params.go similarity index 98% rename from x/distribution/client/cli/util.go rename to x/distribution/client/common/pretty_params.go index 06e24a6b7e60..cbe22869b7d8 100644 --- a/x/distribution/client/cli/util.go +++ b/x/distribution/client/common/pretty_params.go @@ -1,4 +1,4 @@ -package cli +package common import ( "encoding/json" diff --git a/x/distribution/client/module_client.go b/x/distribution/client/module_client.go index 412bf9d6a439..b9dcb1c82bac 100644 --- a/x/distribution/client/module_client.go +++ b/x/distribution/client/module_client.go @@ -46,6 +46,7 @@ func (mc ModuleClient) GetTxCmd() *cobra.Command { distTxCmd.AddCommand(client.PostCommands( distCmds.GetCmdWithdrawRewards(mc.cdc), distCmds.GetCmdSetWithdrawAddr(mc.cdc), + distCmds.GetCmdWithdrawAllRewards(mc.cdc, mc.storeKey), )...) return distTxCmd diff --git a/x/distribution/client/rest/query.go b/x/distribution/client/rest/query.go new file mode 100644 index 000000000000..6f7730407dde --- /dev/null +++ b/x/distribution/client/rest/query.go @@ -0,0 +1,244 @@ +package rest + +import ( + "fmt" + "net/http" + + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/distribution/client/common" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/rest" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/gorilla/mux" +) + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, + cdc *codec.Codec, queryRoute string) { + + // Get the total rewards balance from all delegations + r.HandleFunc( + "/distribution/delegators/{delegatorAddr}/rewards", + delegatorRewardsHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + + // Query a delegation reward + r.HandleFunc( + "/distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}", + delegationRewardsHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + + // Get the rewards withdrawal address + r.HandleFunc( + "/distribution/delegators/{delegatorAddr}/withdraw_address", + delegatorWithdrawalAddrHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + + // Validator distribution information + r.HandleFunc( + "/distribution/validators/{validatorAddr}", + validatorInfoHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + + // Commission and self-delegation rewards of a single a validator + r.HandleFunc( + "/distribution/validators/{validatorAddr}/rewards", + validatorRewardsHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + + // Get the current distribution parameter values + r.HandleFunc( + "/distribution/parameters", + paramsHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") + + // Get the current distribution pool + r.HandleFunc( + "/distribution/outstanding_rewards", + outstandingRewardsHandlerFn(cliCtx, cdc, queryRoute), + ).Methods("GET") +} + +// HTTP request handler to query the total rewards balance from all delegations +func delegatorRewardsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + // query for rewards from a particular delegator + res, ok := checkResponseQueryDelegatorTotalRewards(w, cliCtx, cdc, queryRoute, + mux.Vars(r)["delegatorAddr"]) + if !ok { + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +// HTTP request handler to query a delegation rewards +func delegationRewardsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + // query for rewards from a particular delegation + res, ok := checkResponseQueryDelegationRewards(w, cliCtx, cdc, queryRoute, + mux.Vars(r)["delegatorAddr"], mux.Vars(r)["validatorAddr"]) + if !ok { + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +// HTTP request handler to query a delegation rewards +func delegatorWithdrawalAddrHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + delegatorAddr, ok := checkDelegatorAddressVar(w, r) + if !ok { + return + } + + bz := cdc.MustMarshalJSON(distribution.NewQueryDelegatorWithdrawAddrParams(delegatorAddr)) + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/withdraw_addr", queryRoute), bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +// ValidatorDistInfo defines the properties of +// validator distribution information response. +type ValidatorDistInfo struct { + OperatorAddress sdk.AccAddress `json:"operator_addr"` + SelfBondRewards sdk.DecCoins `json:"self_bond_rewards"` + ValidatorCommission types.ValidatorAccumulatedCommission `json:"val_commission"` +} + +// NewValidatorDistInfo creates a new instance of ValidatorDistInfo. +func NewValidatorDistInfo(operatorAddr sdk.AccAddress, rewards sdk.DecCoins, + commission types.ValidatorAccumulatedCommission) ValidatorDistInfo { + return ValidatorDistInfo{ + OperatorAddress: operatorAddr, + SelfBondRewards: rewards, + ValidatorCommission: commission, + } +} + +// HTTP request handler to query validator's distribution information +func validatorInfoHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + valAddr := mux.Vars(r)["validatorAddr"] + validatorAddr, ok := checkValidatorAddressVar(w, r) + if !ok { + return + } + + // query commission + commissionRes, err := common.QueryValidatorCommission(cliCtx, cdc, queryRoute, validatorAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + var valCom types.ValidatorAccumulatedCommission + cdc.MustUnmarshalJSON(commissionRes, &valCom) + + // self bond rewards + delAddr := sdk.AccAddress(validatorAddr) + rewardsRes, ok := checkResponseQueryDelegationRewards(w, cliCtx, cdc, queryRoute, + delAddr.String(), valAddr) + if !ok { + return + } + + var rewards sdk.DecCoins + cdc.MustUnmarshalJSON(rewardsRes, &rewards) + + // Prepare response + res := cdc.MustMarshalJSON(NewValidatorDistInfo(delAddr, rewards, valCom)) + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +// HTTP request handler to query validator's commission and self-delegation rewards +func validatorRewardsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + valAddr := mux.Vars(r)["validatorAddr"] + validatorAddr, ok := checkValidatorAddressVar(w, r) + if !ok { + return + } + + delAddr := sdk.AccAddress(validatorAddr).String() + res, ok := checkResponseQueryDelegationRewards(w, cliCtx, cdc, queryRoute, delAddr, valAddr) + if !ok { + return + } + + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +// HTTP request handler to query the distribution params values +func paramsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + params, err := common.QueryParams(cliCtx, queryRoute) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + rest.PostProcessResponse(w, cdc, params, cliCtx.Indent) + } +} + +// HTTP request handler to query the outstanding rewards +func outstandingRewardsHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute string) http.HandlerFunc { + + return func(w http.ResponseWriter, r *http.Request) { + res, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/outstanding_rewards", queryRoute), []byte{}) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + rest.PostProcessResponse(w, cdc, res, cliCtx.Indent) + } +} + +func checkResponseQueryDelegatorTotalRewards(w http.ResponseWriter, cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute, delAddr string) (res []byte, ok bool) { + + res, err := common.QueryDelegatorTotalRewards(cliCtx, cdc, queryRoute, delAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return nil, false + } + + return res, true +} + +func checkResponseQueryDelegationRewards(w http.ResponseWriter, cliCtx context.CLIContext, cdc *codec.Codec, + queryRoute, delAddr, valAddr string) (res []byte, ok bool) { + + res, err := common.QueryDelegationRewards(cliCtx, cdc, queryRoute, delAddr, valAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return nil, false + } + + return res, true +} diff --git a/x/distribution/client/rest/rest.go b/x/distribution/client/rest/rest.go new file mode 100644 index 000000000000..769e7360e8da --- /dev/null +++ b/x/distribution/client/rest/rest.go @@ -0,0 +1,13 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/gorilla/mux" +) + +// RegisterRoutes register distribution REST routes. +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec, queryRoute string) { + registerQueryRoutes(cliCtx, r, cdc, queryRoute) + registerTxRoutes(cliCtx, r, cdc, queryRoute) +} diff --git a/x/distribution/client/rest/tx.go b/x/distribution/client/rest/tx.go new file mode 100644 index 000000000000..8e5bff39f35d --- /dev/null +++ b/x/distribution/client/rest/tx.go @@ -0,0 +1,222 @@ +package rest + +import ( + "net/http" + + "github.com/cosmos/cosmos-sdk/client/rest" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/distribution/client/common" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router, + cdc *codec.Codec, queryRoute string) { + + // Withdraw all delegator rewards + r.HandleFunc( + "/distribution/delegators/{delegatorAddr}/rewards", + withdrawDelegatorRewardsHandlerFn(cdc, cliCtx, queryRoute), + ).Methods("POST") + + // Withdraw delegation rewards + r.HandleFunc( + "/distribution/delegators/{delegatorAddr}/rewards/{validatorAddr}", + withdrawDelegationRewardsHandlerFn(cdc, cliCtx), + ).Methods("POST") + + // Replace the rewards withdrawal address + r.HandleFunc( + "/distribution/delegators/{delegatorAddr}/withdraw_address", + setDelegatorWithdrawalAddrHandlerFn(cdc, cliCtx), + ).Methods("POST") + + // Withdraw validator rewards and commission + r.HandleFunc( + "/distribution/validators/{validatorAddr}/rewards", + withdrawValidatorRewardsHandlerFn(cdc, cliCtx), + ).Methods("POST") + +} + +type ( + withdrawRewardsReq struct { + BaseReq rest.BaseReq `json:"base_req"` + } + + setWithdrawalAddrReq struct { + BaseReq rest.BaseReq `json:"base_req"` + WithdrawAddress sdk.AccAddress `json:"withdraw_address"` + } +) + +// Withdraw delegator rewards +func withdrawDelegatorRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext, + queryRoute string) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req withdrawRewardsReq + if !rest.ReadRESTReq(w, r, cdc, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + // read and validate URL's variables + delAddr, ok := checkDelegatorAddressVar(w, r) + if !ok { + return + } + + msgs, err := common.WithdrawAllDelegatorRewards(cliCtx, cdc, queryRoute, delAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, msgs) + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, msgs, cdc) + } +} + +// Withdraw delegation rewards +func withdrawDelegationRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req withdrawRewardsReq + + if !rest.ReadRESTReq(w, r, cdc, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + // read and validate URL's variables + delAddr, ok := checkDelegatorAddressVar(w, r) + if !ok { + return + } + + valAddr, ok := checkValidatorAddressVar(w, r) + if !ok { + return + } + + msg := types.NewMsgWithdrawDelegatorReward(delAddr, valAddr) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + } +} + +// Replace the rewards withdrawal address +func setDelegatorWithdrawalAddrHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req setWithdrawalAddrReq + + if !rest.ReadRESTReq(w, r, cdc, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + // read and validate URL's variables + delAddr, ok := checkDelegatorAddressVar(w, r) + if !ok { + return + } + + msg := types.NewMsgSetWithdrawAddress(delAddr, req.WithdrawAddress) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, []sdk.Msg{msg}) + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, []sdk.Msg{msg}, cdc) + } +} + +// Withdraw validator rewards and commission +func withdrawValidatorRewardsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req withdrawRewardsReq + + if !rest.ReadRESTReq(w, r, cdc, &req) { + return + } + + req.BaseReq = req.BaseReq.Sanitize() + if !req.BaseReq.ValidateBasic(w) { + return + } + + // read and validate URL's variable + valAddr, ok := checkValidatorAddressVar(w, r) + if !ok { + return + } + + // prepare multi-message transaction + msgs, err := common.WithdrawValidatorRewardsAndCommission(valAddr) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + if req.BaseReq.GenerateOnly { + rest.WriteGenerateStdTxResponse(w, cdc, cliCtx, req.BaseReq, msgs) + return + } + + rest.CompleteAndBroadcastTxREST(w, r, cliCtx, req.BaseReq, msgs, cdc) + } +} + +// Auxiliary + +func checkDelegatorAddressVar(w http.ResponseWriter, r *http.Request) (sdk.AccAddress, bool) { + addr, err := sdk.AccAddressFromBech32(mux.Vars(r)["delegatorAddr"]) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return nil, false + } + return addr, true +} + +func checkValidatorAddressVar(w http.ResponseWriter, r *http.Request) (sdk.ValAddress, bool) { + addr, err := sdk.ValAddressFromBech32(mux.Vars(r)["validatorAddr"]) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return nil, false + } + return addr, true +} diff --git a/x/distribution/keeper/querier.go b/x/distribution/keeper/querier.go index 619f2c39eae0..d8948f2fbd86 100644 --- a/x/distribution/keeper/querier.go +++ b/x/distribution/keeper/querier.go @@ -12,12 +12,14 @@ import ( // nolint const ( - QueryParams = "params" - QueryOutstandingRewards = "outstanding_rewards" - QueryValidatorCommission = "validator_commission" - QueryValidatorSlashes = "validator_slashes" - QueryDelegationRewards = "delegation_rewards" - QueryAllDelegationRewards = "all_delegation_rewards" + QueryParams = "params" + QueryOutstandingRewards = "outstanding_rewards" + QueryValidatorCommission = "validator_commission" + QueryValidatorSlashes = "validator_slashes" + QueryDelegationRewards = "delegation_rewards" + QueryDelegatorTotalRewards = "delegator_total_rewards" + QueryDelegatorValidators = "delegator_validators" + QueryWithdrawAddr = "withdraw_addr" ParamCommunityTax = "community_tax" ParamBaseProposerReward = "base_proposer_reward" @@ -43,8 +45,14 @@ func NewQuerier(k Keeper) sdk.Querier { case QueryDelegationRewards: return queryDelegationRewards(ctx, path[1:], req, k) - case QueryAllDelegationRewards: - return queryAllDelegationRewards(ctx, path[1:], req, k) + case QueryDelegatorTotalRewards: + return queryDelegatorTotalRewards(ctx, path[1:], req, k) + + case QueryDelegatorValidators: + return queryDelegatorValidators(ctx, path[1:], req, k) + + case QueryWithdrawAddr: + return queryDelegatorWithdrawAddress(ctx, path[1:], req, k) default: return nil, sdk.ErrUnknownRequest("unknown distr query endpoint") @@ -190,8 +198,20 @@ func queryDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, return bz, nil } -func queryAllDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params QueryDelegationRewardsParams +// params for query 'custom/distr/delegator_total_rewards' and 'custom/distr/delegator_validators' +type QueryDelegatorParams struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` +} + +// creates a new instance of QueryDelegationRewardsParams +func NewQueryDelegatorParams(delegatorAddr sdk.AccAddress) QueryDelegatorParams { + return QueryDelegatorParams{ + DelegatorAddr: delegatorAddr, + } +} + +func queryDelegatorTotalRewards(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryDelegatorParams err := k.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) @@ -221,3 +241,59 @@ func queryAllDelegationRewards(ctx sdk.Context, _ []string, req abci.RequestQuer return bz, nil } + +func queryDelegatorValidators(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryDelegatorParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + + // cache-wrap context as to not persist state changes during querying + ctx, _ = ctx.CacheContext() + + var validators []sdk.ValAddress + + k.stakingKeeper.IterateDelegations( + ctx, params.DelegatorAddr, + func(_ int64, del sdk.Delegation) (stop bool) { + validators = append(validators[:], del.GetValidatorAddr()) + return false + }, + ) + + bz, err := codec.MarshalJSONIndent(k.cdc, validators) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} + +// params for query 'custom/distr/withdraw_addr' +type QueryDelegatorWithdrawAddrParams struct { + DelegatorAddr sdk.AccAddress `json:"delegator_addr"` +} + +// NewQueryDelegatorWithdrawAddrParams creates a new instance of QueryDelegatorWithdrawAddrParams. +func NewQueryDelegatorWithdrawAddrParams(delegatorAddr sdk.AccAddress) QueryDelegatorWithdrawAddrParams { + return QueryDelegatorWithdrawAddrParams{DelegatorAddr: delegatorAddr} +} + +func queryDelegatorWithdrawAddress(ctx sdk.Context, _ []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryDelegatorWithdrawAddrParams + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + + // cache-wrap context as to not persist state changes during querying + ctx, _ = ctx.CacheContext() + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, params.DelegatorAddr) + + bz, err := codec.MarshalJSONIndent(k.cdc, withdrawAddr) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + + return bz, nil +} diff --git a/x/gov/client/rest/rest.go b/x/gov/client/rest/rest.go index e37f7870ecca..9d040d2060e2 100644 --- a/x/gov/client/rest/rest.go +++ b/x/gov/client/rest/rest.go @@ -77,9 +77,7 @@ type voteReq struct { func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var req postProposalReq - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + if !rest.ReadRESTReq(w, r, cdc, &req) { return } @@ -96,8 +94,7 @@ func postProposalHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Han // create the message msg := gov.NewMsgSubmitProposal(req.Title, req.Description, proposalType, req.Proposer, req.InitialDeposit) - err = msg.ValidateBasic() - if err != nil { + if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -128,8 +125,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF } var req depositReq - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { + if !rest.ReadRESTReq(w, r, cdc, &req) { return } @@ -140,8 +136,7 @@ func depositHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerF // create the message msg := gov.NewMsgDeposit(req.Depositor, proposalID, req.Amount) - err = msg.ValidateBasic() - if err != nil { + if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -172,8 +167,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc } var req voteReq - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { + if !rest.ReadRESTReq(w, r, cdc, &req) { return } @@ -190,8 +184,7 @@ func voteHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.HandlerFunc // create the message msg := gov.NewMsgVote(req.Voter, proposalID, voteOption) - err = msg.ValidateBasic() - if err != nil { + if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } diff --git a/x/ibc/client/rest/transfer.go b/x/ibc/client/rest/transfer.go index fd35764b0dbf..f3ca528dce80 100644 --- a/x/ibc/client/rest/transfer.go +++ b/x/ibc/client/rest/transfer.go @@ -39,8 +39,7 @@ func TransferRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. } var req transferReq - err = rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { + if !rest.ReadRESTReq(w, r, cdc, &req) { return } diff --git a/x/slashing/client/rest/tx.go b/x/slashing/client/rest/tx.go index edbfaec92a2b..609da021c468 100644 --- a/x/slashing/client/rest/tx.go +++ b/x/slashing/client/rest/tx.go @@ -34,8 +34,7 @@ func unjailRequestHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context.CL bech32validator := vars["validatorAddr"] var req UnjailReq - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { + if !rest.ReadRESTReq(w, r, cdc, &req) { return } diff --git a/x/staking/client/rest/tx.go b/x/staking/client/rest/tx.go index 397e61a73370..6258d7a7e1ab 100644 --- a/x/staking/client/rest/tx.go +++ b/x/staking/client/rest/tx.go @@ -58,9 +58,7 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. return func(w http.ResponseWriter, r *http.Request) { var req msgDelegationsInput - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + if !rest.ReadRESTReq(w, r, cdc, &req) { return } @@ -70,8 +68,7 @@ func postDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx context. } msg := staking.NewMsgDelegate(req.DelegatorAddr, req.ValidatorAddr, req.Delegation) - err = msg.ValidateBasic() - if err != nil { + if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -103,9 +100,7 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex return func(w http.ResponseWriter, r *http.Request) { var req msgBeginRedelegateInput - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + if !rest.ReadRESTReq(w, r, cdc, &req) { return } @@ -115,8 +110,7 @@ func postRedelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx contex } msg := staking.NewMsgBeginRedelegate(req.DelegatorAddr, req.ValidatorSrcAddr, req.ValidatorDstAddr, req.SharesAmount) - err = msg.ValidateBasic() - if err != nil { + if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } @@ -148,9 +142,7 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx return func(w http.ResponseWriter, r *http.Request) { var req msgUndelegateInput - err := rest.ReadRESTReq(w, r, cdc, &req) - if err != nil { - rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + if !rest.ReadRESTReq(w, r, cdc, &req) { return } @@ -160,8 +152,7 @@ func postUnbondingDelegationsHandlerFn(cdc *codec.Codec, kb keys.Keybase, cliCtx } msg := staking.NewMsgUndelegate(req.DelegatorAddr, req.ValidatorAddr, req.SharesAmount) - err = msg.ValidateBasic() - if err != nil { + if err := msg.ValidateBasic(); err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return }