Skip to content

Commit

Permalink
incentive: set up KVStore schema, parameters and query API (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
SebastianElvis authored Sep 1, 2023
1 parent de37367 commit 5a92ef6
Show file tree
Hide file tree
Showing 26 changed files with 1,883 additions and 75 deletions.
2 changes: 1 addition & 1 deletion proto/babylon/incentive/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ option go_package = "github.com/babylonchain/babylon/x/incentive/types";

// GenesisState defines the incentive module's genesis state.
message GenesisState {
Params params = 1 [(gogoproto.nullable) = false];
Params params = 1 [(gogoproto.nullable) = false];
}
39 changes: 39 additions & 0 deletions proto/babylon/incentive/incentive.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
syntax = "proto3";
package babylon.incentive;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/babylonchain/babylon/x/incentive/types";

// Gauge is an object that stores rewards to be distributed
// code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto
message Gauge {
// coins are coins that have been in the gauge
// Can have multiple coin denoms
repeated cosmos.base.v1beta1.Coin coins = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// distributed_coins are coins that have been distributed already
repeated cosmos.base.v1beta1.Coin distributed_coins = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}

// RewardGauge is an object that stores rewards distributed to a BTC staking/timestamping stakeholder
// code adapted from https://github.com/osmosis-labs/osmosis/blob/v18.0.0/proto/osmosis/incentives/gauge.proto
message RewardGauge {
// coins are coins that have been in the gauge
// Can have multiple coin denoms
repeated cosmos.base.v1beta1.Coin coins = 1 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
// withdrawn_coins are coins that have been withdrawn by the stakeholder already
repeated cosmos.base.v1beta1.Coin withdrawn_coins = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
30 changes: 27 additions & 3 deletions proto/babylon/incentive/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,35 @@ syntax = "proto3";
package babylon.incentive;

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";

option go_package = "github.com/babylonchain/babylon/x/incentive/types";

// Params defines the parameters for the module.
// Params defines the parameters for the module, including portions of rewards
// distributed to each type of stakeholder. Note that sum of the portions should
// be strictly less than 1 so that the rest will go to Tendermint validators/delegations
// adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/proto/cosmos/distribution/v1beta1/distribution.proto
message Params {
option (gogoproto.goproto_stringer) = false;

option (gogoproto.goproto_stringer) = false;

// submitter_portion is the portion of rewards that goes to submitter
string submitter_portion = 1 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// reporter_portion is the portion of rewards that goes to reporter
string reporter_portion = 2 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// btc_staking_portion is the portion of rewards that goes to BTC validators/delegations
// NOTE: the portion of each BTC validator/delegation is calculated by using its voting
// power and BTC validator's commission
string btc_staking_portion = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
32 changes: 26 additions & 6 deletions proto/babylon/incentive/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,42 @@ package babylon.incentive;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "babylon/incentive/params.proto";
import "babylon/incentive/incentive.proto";

option go_package = "github.com/babylonchain/babylon/x/incentive/types";

// Query defines the gRPC querier service.
service Query {
// Parameters queries the parameters of the module.
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/babylonchain/babylon/incentive/params";
}
// Parameters queries the parameters of the module.
rpc Params(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http).get = "/babylonchain/babylon/incentive/params";
}
// RewardGauge queries the reward gauge of a given stakeholder in a given type
rpc RewardGauge(QueryRewardGaugeRequest) returns (QueryRewardGaugeResponse) {
option (google.api.http).get = "/babylonchain/babylon/incentive/{type}/address/{address}/reward_gauge";
}
}

// QueryParamsRequest is request type for the Query/Params RPC method.
message QueryParamsRequest {}

// QueryParamsResponse is response type for the Query/Params RPC method.
message QueryParamsResponse {
// params holds all the parameters of this module.
Params params = 1 [(gogoproto.nullable) = false];
// params holds all the parameters of this module.
Params params = 1 [(gogoproto.nullable) = false];
}

// QueryRewardGaugeRequest is request type for the Query/RewardGauge RPC method.
message QueryRewardGaugeRequest {
// type is the type of the stakeholder, can be one of
// {submitter, reporter, btc_validator, btc_delegation}
string type = 1;
// address is the address of the stakeholder in bech32 string
string address = 2;
}

// QueryParamsResponse is response type for the Query/RewardGauge RPC method.
message QueryRewardGaugeResponse {
// reward_gauge is the reward gauge holding all rewards for the stakeholder
RewardGauge reward_gauge = 1;
}
46 changes: 46 additions & 0 deletions testutil/datagen/incentive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package datagen

import (
"math/rand"

itypes "github.com/babylonchain/babylon/x/incentive/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

const (
characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
denomLen = 5
)

func GenRandomDenom(r *rand.Rand) string {
var result string
// Generate the random string
for i := 0; i < denomLen; i++ {
// Generate a random index within the range of the character set
index := r.Intn(len(characters))
// Add the randomly selected character to the result
result += string(characters[index])
}
return result
}

func GenRandomStakeholderType(r *rand.Rand) itypes.StakeholderType {
stBytes := []byte{byte(RandomInt(r, 4))}
st, err := itypes.NewStakeHolderType(stBytes)
if err != nil {
panic(err) // only programming error is possible
}
return st
}

func GenRandomRewardGauge(r *rand.Rand) *itypes.RewardGauge {
numCoins := r.Int31n(10) + 10
coins := sdk.NewCoins()
for i := int32(0); i < numCoins; i++ {
demon := GenRandomDenom(r)
amount := r.Int63n(10000)
coin := sdk.NewInt64Coin(demon, amount)
coins = coins.Add(coin)
}
return itypes.NewRewardGauge(coins...)
}
4 changes: 3 additions & 1 deletion testutil/keeper/incentive.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ func IncentiveKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger())

// Initialize params
k.SetParams(ctx, types.DefaultParams())
if err := k.SetParams(ctx, types.DefaultParams()); err != nil {
panic(err)
}

return &k, ctx
}
4 changes: 3 additions & 1 deletion x/incentive/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (

// InitGenesis initializes the module's state from a provided genesis state.
func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) {
k.SetParams(ctx, genState.Params)
if err := k.SetParams(ctx, genState.Params); err != nil {
panic(err)
}
}

// ExportGenesis returns the module's exported genesis
Expand Down
35 changes: 35 additions & 0 deletions x/incentive/keeper/btc_staking_gauge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package keeper

import (
"github.com/babylonchain/babylon/x/incentive/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k Keeper) SetBTCStakingGauge(ctx sdk.Context, height uint64, gauge *types.Gauge) {
store := k.btcStakingGaugeStore(ctx)
gaugeBytes := k.cdc.MustMarshal(gauge)
store.Set(sdk.Uint64ToBigEndian(height), gaugeBytes)
}

func (k Keeper) GetBTCStakingGauge(ctx sdk.Context, height uint64) (*types.Gauge, error) {
store := k.btcStakingGaugeStore(ctx)
gaugeBytes := store.Get(sdk.Uint64ToBigEndian(height))
if len(gaugeBytes) == 0 {
return nil, types.ErrBTCStakingGaugeNotFound
}

var gauge types.Gauge
k.cdc.MustUnmarshal(gaugeBytes, &gauge)
return &gauge, nil
}

// btcStakingGaugeStore returns the KVStore of the gauge of total reward for
// BTC staking at each height
// prefix: BTCStakingGaugeKey
// key: gauge height
// value: gauge of rewards for BTC staking at this height
func (k Keeper) btcStakingGaugeStore(ctx sdk.Context) prefix.Store {
store := ctx.KVStore(k.storeKey)
return prefix.NewStore(store, types.BTCStakingGaugeKey)
}
35 changes: 35 additions & 0 deletions x/incentive/keeper/btc_timestamping_gauge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package keeper

import (
"github.com/babylonchain/babylon/x/incentive/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k Keeper) SetBTCTimestampingGauge(ctx sdk.Context, epoch uint64, gauge *types.Gauge) {
store := k.btcTimestampingGaugeStore(ctx)
gaugeBytes := k.cdc.MustMarshal(gauge)
store.Set(sdk.Uint64ToBigEndian(epoch), gaugeBytes)
}

func (k Keeper) GetBTCTimestampingGauge(ctx sdk.Context, epoch uint64) (*types.Gauge, error) {
store := k.btcTimestampingGaugeStore(ctx)
gaugeBytes := store.Get(sdk.Uint64ToBigEndian(epoch))
if len(gaugeBytes) == 0 {
return nil, types.ErrBTCTimestampingGaugeNotFound
}

var gauge types.Gauge
k.cdc.MustUnmarshal(gaugeBytes, &gauge)
return &gauge, nil
}

// btcTimestampingGaugeStore returns the KVStore of the gauge of total reward for
// BTC timestamping at each epoch
// prefix: BTCTimestampingGaugeKey
// key: epoch number
// value: gauge of rewards for BTC timestamping at this epoch
func (k Keeper) btcTimestampingGaugeStore(ctx sdk.Context) prefix.Store {
store := ctx.KVStore(k.storeKey)
return prefix.NewStore(store, types.BTCTimestampingGaugeKey)
}
37 changes: 37 additions & 0 deletions x/incentive/keeper/grpc_query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package keeper

import (
"context"

"github.com/babylonchain/babylon/x/incentive/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

var _ types.QueryServer = Keeper{}

func (k Keeper) RewardGauge(goCtx context.Context, req *types.QueryRewardGaugeRequest) (*types.QueryRewardGaugeResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
ctx := sdk.UnwrapSDKContext(goCtx)

// try to cast types for fields in the request
sType, err := types.NewStakeHolderTypeFromString(req.Type)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
address, err := sdk.AccAddressFromBech32(req.Address)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

// find reward gauge
rg, err := k.GetRewardGauge(ctx, sType, address)
if err != nil {
return nil, err
}

return &types.QueryRewardGaugeResponse{RewardGauge: rg}, nil
}
49 changes: 49 additions & 0 deletions x/incentive/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package keeper_test

import (
"math/rand"
"testing"

"github.com/babylonchain/babylon/testutil/datagen"
testkeeper "github.com/babylonchain/babylon/testutil/keeper"
"github.com/babylonchain/babylon/x/incentive/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)

func FuzzRewardGaugeQuery(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)
f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))

keeper, ctx := testkeeper.IncentiveKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

// generate a list of random RewardGauges and insert them to KVStore
rgList := []*types.RewardGauge{}
sTypeList := []types.StakeholderType{}
sAddrList := []sdk.AccAddress{}
numRgs := datagen.RandomInt(r, 100)
for i := uint64(0); i < numRgs; i++ {
sType := datagen.GenRandomStakeholderType(r)
sTypeList = append(sTypeList, sType)
sAddr := datagen.GenRandomAccount().GetAddress()
sAddrList = append(sAddrList, sAddr)
rg := datagen.GenRandomRewardGauge(r)
rgList = append(rgList, rg)

keeper.SetRewardGauge(ctx, sType, sAddr, rg)
}

// query existence and assert consistency
for i := range rgList {
req := &types.QueryRewardGaugeRequest{
Type: sTypeList[i].String(),
Address: sAddrList[i].String(),
}
resp, err := keeper.RewardGauge(wctx, req)
require.NoError(t, err)
require.True(t, resp.RewardGauge.Coins.IsEqual(rgList[i].Coins))
}
})
}
8 changes: 3 additions & 5 deletions x/incentive/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/babylonchain/babylon/x/incentive/types"
)

type (
Keeper struct {
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
memKey storetypes.StoreKey
paramstore paramtypes.Subspace
cdc codec.BinaryCodec
storeKey storetypes.StoreKey
memKey storetypes.StoreKey

bankKeeper types.BankKeeper
accountKeeper types.AccountKeeper
Expand Down
Loading

0 comments on commit 5a92ef6

Please sign in to comment.