Skip to content

Commit

Permalink
Incentive module: claim savings reward (Kava-Labs#1208)
Browse files Browse the repository at this point in the history
* update savings module macc balances getter

* add savings keeper to incentive module

* add savings keeper to incentive module Kava-Labs#2

* savings reward syncing

* claim savings reward

* update txs, queries

* update txs, queries Kava-Labs#2

* update claim test

* add savings keeper to incentive module in app.go

* re-commit files to disk

* fix: replace swap with savings when querying savings rewards

* update func comment

Co-authored-by: karzak <[email protected]>
  • Loading branch information
denalimarsh and karzak authored Apr 20, 2022
1 parent db5e839 commit c2e53f2
Show file tree
Hide file tree
Showing 21 changed files with 334 additions and 68 deletions.
15 changes: 8 additions & 7 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,13 @@ func NewApp(
app.pricefeedKeeper,
app.auctionKeeper,
)
savingsKeeper := savingskeeper.NewKeeper(
appCodec,
keys[savingstypes.StoreKey],
savingsSubspace,
app.accountKeeper,
app.bankKeeper,
)
app.incentiveKeeper = incentivekeeper.NewKeeper(
appCodec,
keys[incentivetypes.StoreKey],
Expand All @@ -536,13 +543,7 @@ func NewApp(
app.accountKeeper,
app.stakingKeeper,
&swapKeeper,
)
savingsKeeper := savingskeeper.NewKeeper(
appCodec,
keys[savingstypes.StoreKey],
savingsSubspace,
app.accountKeeper,
app.bankKeeper,
&savingsKeeper,
)
// create committee keeper with router
committeeGovRouter := govtypes.NewRouter()
Expand Down
43 changes: 42 additions & 1 deletion x/incentive/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
typeHard = "hard"
typeUSDXMinting = "usdx-minting"
typeSwap = "swap"
typeSavings = "savings"
)

var rewardTypes = []string{typeDelegator, typeHard, typeUSDXMinting, typeSwap}
Expand Down Expand Up @@ -64,13 +65,15 @@ func queryRewardsCmd() *cobra.Command {
$ %s query %s rewards --type usdx-minting
$ %s query %s rewards --type delegator
$ %s query %s rewards --type swap
$ %s query %s rewards --type savings
$ %s query %s rewards --type hard --owner kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw
$ %s query %s rewards --type hard --unsynced
`,
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
version.AppName, types.ModuleName, version.AppName, types.ModuleName)),
version.AppName, types.ModuleName, version.AppName, types.ModuleName,
version.AppName, types.ModuleName)),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx, err := client.GetClientQueryContext(cmd)
Expand Down Expand Up @@ -121,6 +124,13 @@ func queryRewardsCmd() *cobra.Command {
return err
}
return cliCtx.PrintObjectLegacy(claims)
case typeSavings:
params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced)
claims, err := executeSavingsRewardsQuery(cliCtx, params)
if err != nil {
return err
}
return cliCtx.PrintObjectLegacy(claims)
default:
params := types.NewQueryRewardsParams(page, limit, owner, boolUnsynced)

Expand All @@ -140,6 +150,10 @@ func queryRewardsCmd() *cobra.Command {
if err != nil {
return err
}
savingsClaims, err := executeSavingsRewardsQuery(cliCtx, params)
if err != nil {
return err
}
if len(hardClaims) > 0 {
if err := cliCtx.PrintObjectLegacy(hardClaims); err != nil {
return err
Expand All @@ -160,6 +174,11 @@ func queryRewardsCmd() *cobra.Command {
return err
}
}
if len(savingsClaims) > 0 {
if err := cliCtx.PrintObjectLegacy(savingsClaims); err != nil {
return err
}
}
}
return nil
},
Expand Down Expand Up @@ -321,3 +340,25 @@ func executeSwapRewardsQuery(cliCtx client.Context, params types.QueryRewardsPar

return claims, nil
}

func executeSavingsRewardsQuery(cliCtx client.Context, params types.QueryRewardsParams) (types.SavingsClaims, error) {
bz, err := cliCtx.LegacyAmino.MarshalJSON(params)
if err != nil {
return types.SavingsClaims{}, err
}

route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetSavingsRewards)
res, height, err := cliCtx.QueryWithData(route, bz)
if err != nil {
return types.SavingsClaims{}, err
}

cliCtx = cliCtx.WithHeight(height)

var claims types.SavingsClaims
if err := cliCtx.LegacyAmino.UnmarshalJSON(res, &claims); err != nil {
return types.SavingsClaims{}, fmt.Errorf("failed to unmarshal claims: %w", err)
}

return claims, nil
}
36 changes: 36 additions & 0 deletions x/incentive/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func GetTxCmd() *cobra.Command {
getCmdClaimHard(),
getCmdClaimDelegator(),
getCmdClaimSwap(),
getCmdClaimSavings(),
}

for _, cmd := range cmds {
Expand Down Expand Up @@ -172,3 +173,38 @@ func getCmdClaimSwap() *cobra.Command {
}
return cmd
}

func getCmdClaimSavings() *cobra.Command {
var denomsToClaim map[string]string

cmd := &cobra.Command{
Use: "claim-savings",
Short: "claim sender's savings rewards using given multipliers",
Long: `Claim sender's outstanding savings rewards using given multipliers`,
Example: strings.Join([]string{
fmt.Sprintf(` $ %s tx %s claim-savings --%s swp=large --%s ukava=small`, version.AppName, types.ModuleName, multiplierFlag, multiplierFlag),
fmt.Sprintf(` $ %s tx %s claim-savings --%s swp=large,ukava=small`, version.AppName, types.ModuleName, multiplierFlag),
}, "\n"),
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

sender := cliCtx.GetFromAddress()
selections := types.NewSelectionsFromMap(denomsToClaim)

msg := types.NewMsgClaimSavingsReward(sender.String(), selections)
if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(cliCtx, cmd.Flags(), &msg)
},
}
cmd.Flags().StringToStringVarP(&denomsToClaim, multiplierFlag, multiplierFlagShort, nil, "specify the denoms to claim, each with a multiplier lockup")
if err := cmd.MarkFlagRequired(multiplierFlag); err != nil {
panic(err)
}
return cmd
}
45 changes: 44 additions & 1 deletion x/incentive/keeper/claim.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,49 @@ func (k Keeper) ClaimSwapReward(ctx sdk.Context, owner, receiver sdk.AccAddress,

// ClaimSavingsReward is a stub method for MsgServer interface compliance
func (k Keeper) ClaimSavingsReward(ctx sdk.Context, owner, receiver sdk.AccAddress, denom string, multiplierName string) error {
// TODO: implement savings claim logic
multiplier, found := k.GetMultiplierByDenom(ctx, denom, multiplierName)
if !found {
return sdkerrors.Wrapf(types.ErrInvalidMultiplier, "denom '%s' has no multiplier '%s'", denom, multiplierName)
}

claimEnd := k.GetClaimEnd(ctx)

if ctx.BlockTime().After(claimEnd) {
return sdkerrors.Wrapf(types.ErrClaimExpired, "block time %s > claim end time %s", ctx.BlockTime(), claimEnd)
}

k.SynchronizeSavingsClaim(ctx, owner)

syncedClaim, found := k.GetSavingsClaim(ctx, owner)
if !found {
return sdkerrors.Wrapf(types.ErrClaimNotFound, "address: %s", owner)
}

amt := syncedClaim.Reward.AmountOf(denom)

claimingCoins := sdk.NewCoins(sdk.NewCoin(denom, amt))
rewardCoins := sdk.NewCoins(sdk.NewCoin(denom, amt.ToDec().Mul(multiplier.Factor).RoundInt()))
if rewardCoins.IsZero() {
return types.ErrZeroClaim
}
length := k.GetPeriodLength(ctx.BlockTime(), multiplier.MonthsLockup)

err := k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, receiver, rewardCoins, length)
if err != nil {
return err
}

// remove claimed coins (NOT reward coins)
syncedClaim.Reward = syncedClaim.Reward.Sub(claimingCoins)
k.SetSavingsClaim(ctx, syncedClaim)

ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaim,
sdk.NewAttribute(types.AttributeKeyClaimedBy, owner.String()),
sdk.NewAttribute(types.AttributeKeyClaimAmount, claimingCoins.String()),
sdk.NewAttribute(types.AttributeKeyClaimType, syncedClaim.GetType()),
),
)
return nil
}
4 changes: 2 additions & 2 deletions x/incentive/keeper/claim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (suite *ClaimTests) TestCannotClaimWhenMultiplierNotRecognised() {
},
},
}
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil)
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil)

claim := types.DelegatorClaim{
BaseMultiClaim: types.BaseMultiClaim{
Expand Down Expand Up @@ -70,7 +70,7 @@ func (suite *ClaimTests) TestCannotClaimAfterEndTime() {
ClaimEnd: endTime,
},
}
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil)
suite.keeper = suite.NewKeeper(subspace, nil, nil, nil, nil, nil, nil, nil)

suite.ctx = suite.ctx.WithBlockTime(endTime.Add(time.Nanosecond))

Expand Down
4 changes: 3 additions & 1 deletion x/incentive/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ type Keeper struct {
hardKeeper types.HardKeeper
stakingKeeper types.StakingKeeper
swapKeeper types.SwapKeeper
savingsKeeper types.SavingsKeeper
}

// NewKeeper creates a new keeper
func NewKeeper(
cdc codec.Codec, key sdk.StoreKey, paramstore types.ParamSubspace, bk types.BankKeeper,
cdpk types.CdpKeeper, hk types.HardKeeper, ak types.AccountKeeper, stk types.StakingKeeper,
swpk types.SwapKeeper,
swpk types.SwapKeeper, svk types.SavingsKeeper,
) Keeper {

if !paramstore.HasKeyTable() {
Expand All @@ -44,6 +45,7 @@ func NewKeeper(
hardKeeper: hk,
stakingKeeper: stk,
swapKeeper: swpk,
savingsKeeper: svk,
}
}

Expand Down
55 changes: 54 additions & 1 deletion x/incentive/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func NewQuerier(k Keeper, legacyQuerierCdc *codec.LegacyAmino) sdk.Querier {
return queryGetDelegatorRewards(ctx, req, k, legacyQuerierCdc)
case types.QueryGetSwapRewards:
return queryGetSwapRewards(ctx, req, k, legacyQuerierCdc)

case types.QueryGetSavingsRewards:
return queryGetSavingsRewards(ctx, req, k, legacyQuerierCdc)
case types.QueryGetRewardFactors:
return queryGetRewardFactors(ctx, req, k, legacyQuerierCdc)
default:
Expand Down Expand Up @@ -215,6 +216,51 @@ func queryGetSwapRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper, legac
return bz, nil
}

func queryGetSavingsRewards(ctx sdk.Context, req abci.RequestQuery, k Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {
var params types.QueryRewardsParams
err := legacyQuerierCdc.UnmarshalJSON(req.Data, &params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
owner := len(params.Owner) > 0

var claims types.SavingsClaims
switch {
case owner:
claim, found := k.GetSavingsClaim(ctx, params.Owner)
if found {
claims = append(claims, claim)
}
default:
claims = k.GetAllSavingsClaims(ctx)
}

var paginatedClaims types.SavingsClaims
startH, endH := client.Paginate(len(claims), params.Page, params.Limit, 100)
if startH < 0 || endH < 0 {
paginatedClaims = types.SavingsClaims{}
} else {
paginatedClaims = claims[startH:endH]
}

if !params.Unsynchronized {
for i, claim := range paginatedClaims {
syncedClaim, found := k.GetSynchronizedSavingsClaim(ctx, claim.Owner)
if !found {
panic("previously found claim should still be found")
}
paginatedClaims[i] = syncedClaim
}
}

// Marshal claims
bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, paginatedClaims)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}

func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper, legacyQuerierCdc *codec.LegacyAmino) ([]byte, error) {

var usdxFactors types.RewardIndexes
Expand Down Expand Up @@ -247,12 +293,19 @@ func queryGetRewardFactors(ctx sdk.Context, req abci.RequestQuery, k Keeper, leg
return false
})

var savingsFactors types.MultiRewardIndexes
k.IterateSavingsRewardIndexes(ctx, func(denom string, indexes types.RewardIndexes) (stop bool) {
savingsFactors = savingsFactors.With(denom, indexes)
return false
})

response := types.NewQueryGetRewardFactorsResponse(
usdxFactors,
supplyFactors,
borrowFactors,
delegatorFactors,
swapFactors,
savingsFactors,
)

bz, err := codec.MarshalJSONIndent(legacyQuerierCdc, response)
Expand Down
Loading

0 comments on commit c2e53f2

Please sign in to comment.