Skip to content

Commit

Permalink
chore(incentive): add DelegationRewards query (#590)
Browse files Browse the repository at this point in the history
Closes #582
  • Loading branch information
GAtom22 authored Feb 28, 2025
1 parent 6b5cb9a commit 722acb6
Show file tree
Hide file tree
Showing 7 changed files with 773 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ check of rewards
- [#556](https://github.com/babylonlabs-io/babylon/pull/556) Panic on consensus critical errors
- [#566](https://github.com/babylonlabs-io/babylon/pull/566) Remove float values in `BeforeValidatorSlashed` hook in `x/epoching` module
- [#575](https://github.com/babylonlabs-io/babylon/pull/575) Add `ConsumerId` field in `FinalityProviderResponse` in `x/btcstaking` module
- [#590](https://github.com/babylonlabs-io/babylon/pull/590) Add `DelegationRewards` query in `x/incentive` module

### State Machine Breaking

Expand Down
38 changes: 38 additions & 0 deletions proto/babylon/incentive/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ service Query {
option (google.api.http).get =
"/babylon/incentive/delegators/{delegator_address}/withdraw_address";
}

// DelegationRewards queries the delegation rewards of given finality provider
// and delegator addresses
rpc DelegationRewards(QueryDelegationRewardsRequest)
returns (QueryDelegationRewardsResponse) {
option (google.api.http).get =
"/babylon/incentive/finality_providers/{finality_provider_address}/"
"delegators/{delegator_address}/delegation_rewards";
}
}

// QueryParamsRequest is request type for the Query/Params RPC method.
Expand Down Expand Up @@ -123,4 +132,33 @@ message QueryDelegatorWithdrawAddressResponse {
// withdraw_address defines the delegator address to query for.
string withdraw_address = 1
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}

// QueryDelegationRewardsRequest is the request type for the
// Query/DelegationRewards RPC method.
message QueryDelegationRewardsRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// finality_provider_address defines the finality provider address of the
// delegation.
string finality_provider_address = 1
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
// delegator_address defines the delegator address to query for.
string delegator_address = 2
[ (cosmos_proto.scalar) = "cosmos.AddressString" ];
}

// QueryDelegationRewardsResponse is the response type for the
// Query/DelegationRewards RPC method.
message QueryDelegationRewardsResponse {
option (gogoproto.equal) = false;
option (gogoproto.goproto_getters) = false;

// rewards are the delegation reward coins
// Can have multiple coin denoms
repeated cosmos.base.v1beta1.Coin rewards = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
32 changes: 32 additions & 0 deletions x/incentive/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
CmdQueryParams(),
CmdQueryRewardGauges(),
CmdQueryBTCStakingGauge(),
CmdQueryDelegationRewards(),
)

return cmd
Expand Down Expand Up @@ -94,3 +95,34 @@ func CmdQueryBTCStakingGauge() *cobra.Command {

return cmd
}

func CmdQueryDelegationRewards() *cobra.Command {
cmd := &cobra.Command{
Use: "delegation-rewards [finality-provider-address] [delegator-address]",
Short: "shows the current delegation rewards of given finality provider and delegator addresses",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryDelegationRewardsRequest{
FinalityProviderAddress: args[0],
DelegatorAddress: args[1],
}
res, err := queryClient.DelegationRewards(cmd.Context(), req)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}
27 changes: 27 additions & 0 deletions x/incentive/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,33 @@ func (k Keeper) BTCStakingGauge(goCtx context.Context, req *types.QueryBTCStakin
return &types.QueryBTCStakingGaugeResponse{Gauge: convertGaugeToBTCStakingResponse(*gauge)}, nil
}

// DelegationRewards returns the current rewards for the specified finality provider and delegator
func (k Keeper) DelegationRewards(ctx context.Context, req *types.QueryDelegationRewardsRequest) (*types.QueryDelegationRewardsResponse, error) {
// try to cast address
fpAddr, err := sdk.AccAddressFromBech32(req.FinalityProviderAddress)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
delAddr, err := sdk.AccAddressFromBech32(req.DelegatorAddress)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

// Finalize the period to get a new history with the current rewards available
// This will not be committed anyways because it is a query
endPeriod, err := k.IncrementFinalityProviderPeriod(ctx, fpAddr)
if err != nil {
return nil, err
}

rewards, err := k.CalculateBTCDelegationRewards(ctx, fpAddr, delAddr, endPeriod)
if err != nil {
return nil, err
}

return &types.QueryDelegationRewardsResponse{Rewards: rewards}, nil
}

func convertGaugeToBTCStakingResponse(gauge types.Gauge) *types.BTCStakingGaugeResponse {
return &types.BTCStakingGaugeResponse{
Coins: gauge.Coins,
Expand Down
65 changes: 65 additions & 0 deletions x/incentive/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ import (
"math/rand"
"testing"

"cosmossdk.io/collections"
"cosmossdk.io/math"
"cosmossdk.io/store/prefix"
storetypes "cosmossdk.io/store/types"
"github.com/babylonlabs-io/babylon/testutil/datagen"
testkeeper "github.com/babylonlabs-io/babylon/testutil/keeper"
"github.com/babylonlabs-io/babylon/x/incentive/types"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -86,3 +91,63 @@ func FuzzBTCStakingGaugeQuery(f *testing.F) {
}
})
}

func FuzzDelegationRewardsQuery(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)
f.Fuzz(func(t *testing.T, seed int64) {
t.Parallel()
var (
r = rand.New(rand.NewSource(seed))
storeKey = storetypes.NewKVStoreKey(types.StoreKey)
k, ctx = testkeeper.IncentiveKeeperWithStoreKey(t, storeKey, nil, nil, nil)
fp, del = datagen.GenRandomAddress(), datagen.GenRandomAddress()
storeService = runtime.NewKVStoreService(storeKey)
store = storeService.OpenKVStore(ctx)
storeAdaptor = runtime.KVStoreAdapter(store)
btcRwd = datagen.GenRandomBTCDelegationRewardsTracker(r)
)

// Setup a BTCDelegationRewardsTracker for the delegator
require.NoError(t, k.BTCDelegationRewardsTracker.Set(ctx, collections.Join(fp.Bytes(), del.Bytes()), btcRwd))
st := prefix.NewStore(storeAdaptor, types.BTCDelegatorToFPKey)
delStore := prefix.NewStore(st, del.Bytes())
delStore.Set(fp.Bytes(), []byte{0x00})

// Setup FP current rewards with the corresponding period
// and same TotalActiveSat than the BTCDelegationRewardsTracker
fpCurrentRwd := datagen.GenRandomFinalityProviderCurrentRewards(r)
fpCurrentRwd.Period = btcRwd.StartPeriodCumulativeReward + 2
fpCurrentRwd.TotalActiveSat = btcRwd.TotalActiveSat
err := k.FinalityProviderCurrentRewards.Set(ctx, fp.Bytes(), fpCurrentRwd)
require.NoError(t, err)

// set start historical rewards corresponding to btcRwd.StartPeriodCumulativeReward
amtRwdInHistStart := fpCurrentRwd.CurrentRewards.MulInt(types.DecimalAccumulatedRewards).QuoInt(math.NewInt(2))
startHist := types.NewFinalityProviderHistoricalRewards(amtRwdInHistStart)
err = k.FinalityProviderHistoricalRewards.Set(ctx, collections.Join(fp.Bytes(), btcRwd.StartPeriodCumulativeReward), startHist)
require.NoError(t, err)

// set end period historical rewards
// end period for calculation is fpCurrentRwd.Period-1
amtRwdInHistEnd := amtRwdInHistStart.Add(fpCurrentRwd.CurrentRewards.MulInt(types.DecimalAccumulatedRewards).QuoInt(fpCurrentRwd.TotalActiveSat)...)
endHist := types.NewFinalityProviderHistoricalRewards(amtRwdInHistEnd)
err = k.FinalityProviderHistoricalRewards.Set(ctx, collections.Join(fp.Bytes(), fpCurrentRwd.Period-1), endHist)
require.NoError(t, err)

// Calculate expected rewards
expectedRwd := endHist.CumulativeRewardsPerSat.Sub(startHist.CumulativeRewardsPerSat...)
expectedRwd = expectedRwd.MulInt(btcRwd.TotalActiveSat.Add(fpCurrentRwd.TotalActiveSat))
expectedRwd = expectedRwd.QuoInt(types.DecimalAccumulatedRewards)

// Call the DelegationRewards query
res, err := k.DelegationRewards(
ctx,
&types.QueryDelegationRewardsRequest{
FinalityProviderAddress: fp.String(),
DelegatorAddress: del.String(),
},
)
require.NoError(t, err)
require.Equal(t, expectedRwd, res.Rewards)
})
}
Loading

0 comments on commit 722acb6

Please sign in to comment.