Skip to content

Commit

Permalink
chore: add spec files and small nits
Browse files Browse the repository at this point in the history
  • Loading branch information
mbreithecker committed Feb 12, 2025
1 parent 0e0bfc5 commit 8d340d2
Show file tree
Hide file tree
Showing 24 changed files with 552 additions and 85 deletions.
12 changes: 11 additions & 1 deletion proto/kyve/multi_coin_rewards/v1beta1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,14 @@ message EventUpdateParams {
string payload = 3;
}

// TODO add events
// EventToggleMultiCoinRewards ...
message EventToggleMultiCoinRewards {
// address ...
string address = 1;

// enabled ...
bool enabled = 2;

// pending_rewards_claimed ...
string pending_rewards_claimed = 3;
}
7 changes: 4 additions & 3 deletions proto/kyve/multi_coin_rewards/v1beta1/params.proto
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ option go_package = "github.com/KYVENetwork/chain/x/multi_coin_rewards/types";
// Params defines the multi_coin_rewards module parameters.
message Params {
// multi_coin_distribution_policy_admin_address specifies an address which is allowed to adjust the weights for
// the coin redistribution. This address can now drain coins, but only
string multi_coin_distribution_policy_admin_address = 7;
// the coin redistribution. This address can not drain coins, but only modify the in which pools coins
// get re-distributed.
string multi_coin_distribution_policy_admin_address = 1;

// multi_coin_distribution_pending_time ...
uint64 multi_coin_distribution_pending_time = 8;
uint64 multi_coin_distribution_pending_time = 2;
}
1 change: 0 additions & 1 deletion x/multi_coin_rewards/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (

// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string) *cobra.Command {
// Group stakers queries under a subcommand
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName),
Expand Down
5 changes: 4 additions & 1 deletion x/multi_coin_rewards/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {

genesis.QueueStatePendingRewards = k.GetQueueState(ctx, types.QUEUE_IDENTIFIER_MULTI_COIN_REWARDS)

policy, _ := k.MultiCoinDistributionPolicy.Get(ctx)
policy, err := k.MultiCoinDistributionPolicy.Get(ctx)
if err != nil {
panic(err)
}
genesis.MultiCoinDistributionPolicy = &policy

genesis.MultiCoinEnabled = k.GetAllEnabledMultiCoinAddresses(ctx)
Expand Down
4 changes: 1 addition & 3 deletions x/multi_coin_rewards/keeper/getters_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)

// GetQueueState returns a queue state object based on the identifier as
// there are multiple queues present in the stakers module
// GetQueueState returns a queue state object based on the identifier
func (k Keeper) GetQueueState(ctx sdk.Context, identifier types.QUEUE_IDENTIFIER) (state types.QueueState) {
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
b := store.Get(identifier)
Expand All @@ -21,7 +20,6 @@ func (k Keeper) GetQueueState(ctx sdk.Context, identifier types.QUEUE_IDENTIFIER
}

// SetQueueState sets a endBlocker queue state based on the identifier.
// The identifier is used to distinguish between different queues.
func (k Keeper) SetQueueState(ctx sdk.Context, identifier types.QUEUE_IDENTIFIER, state types.QueueState) {
store := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
b := k.cdc.MustMarshal(&state)
Expand Down
2 changes: 1 addition & 1 deletion x/multi_coin_rewards/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (k Keeper) MultiCoinDistributionPolicyQuery(ctx context.Context, request *t
if err != nil {
return nil, err
}
return &types.QueryMultiCoinDistributionPolicyResponse{Policy: policy}, err
return &types.QueryMultiCoinDistributionPolicyResponse{Policy: policy}, nil
}

func (k Keeper) MultiCoinStatus(ctx context.Context, request *types.QueryMultiCoinStatusRequest) (*types.QueryMultiCoinStatusResponse, error) {
Expand Down
4 changes: 2 additions & 2 deletions x/multi_coin_rewards/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ func NewKeeper(
bankKeeper: bankKeeper,
poolKeeper: poolKeeper,

MultiCoinRewardsEnabled: collections.NewKeySet(sb, types.MultiCoinRewardsEnabledKeyPrefix,
MultiCoinRewardsEnabled: collections.NewKeySet(sb, types.MultiCoinRewardsEnabledKey,
"multi_coin_rewards_enabled", sdk.AccAddressKey),
MultiCoinDistributionPolicy: collections.NewItem(sb, types.MultiCoinDistributionPolicyKeyPrefix,
MultiCoinDistributionPolicy: collections.NewItem(sb, types.MultiCoinDistributionPolicyKey,
"multi_coin_rewards_policy", codec.CollValue[types.MultiCoinDistributionPolicy](cdc)),
}

Expand Down
2 changes: 1 addition & 1 deletion x/multi_coin_rewards/keeper/keeper_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
. "github.com/onsi/gomega"
)

func TestStakersKeeper(t *testing.T) {
func TestMultiCoinRewardsKeeper(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, fmt.Sprintf("x/%s Keeper Test Suite", types.ModuleName))
}
Expand Down
30 changes: 17 additions & 13 deletions x/multi_coin_rewards/keeper/logic_distribution.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ func (k Keeper) ProcessPendingRewardsQueue(ctx sdk.Context) {
}
}

// DistributeNonClaimedRewards takes all non-claimed rewards which have exceeding the claim time
// and re-distribute them to the pools according to the redistribution-policy.
func (k Keeper) DistributeNonClaimedRewards(ctx sdk.Context) error {
policy, err := k.MultiCoinDistributionPolicy.Get(ctx)
if err != nil {
Expand All @@ -59,26 +61,29 @@ func (k Keeper) DistributeNonClaimedRewards(ctx sdk.Context) error {
}

// Store rewards for all pools. There could be multiple rules which re-direct coins to the same pool
type PoolRewards struct {
type PoolRewardsBasket struct {
account sdk.AccAddress
rewards sdk.Coins
poolId uint64
}
poolRewards := make(map[uint64]PoolRewards)
poolRewardBaskets := make(map[uint64]PoolRewardsBasket)

// Get all rewards
rewards := k.bankKeeper.GetAllBalances(ctx, k.accountKeeper.GetModuleAddress(types.MultiCoinRewardsRedistributionAccountName))

// Iterate every coin denom and re-distribute accordingly
for _, coin := range rewards {
weightMap, ok := distributionMap[coin.Denom]
if !ok {
// Coin not registered in coin map, it will stay in the module account
continue
}

// weight-map contains for every denom the destination pools together with a weight.
for _, weight := range weightMap {
// Check if pool is already in temporary map
accounts, ok := poolRewards[weight.PoolId]
poolBasket, ok := poolRewardBaskets[weight.PoolId]
// If pool is not registered in map yet, initialize new pool basket.
if !ok {
// if not, get pool from id
pool, err := k.poolKeeper.GetPoolWithError(ctx, weight.PoolId)
Expand All @@ -87,34 +92,33 @@ func (k Keeper) DistributeNonClaimedRewards(ctx sdk.Context) error {
return err
}

accounts.poolId = pool.Id
accounts.account = pool.GetPoolAccount()
accounts.rewards = sdk.NewCoins()
poolRewards[weight.PoolId] = accounts
poolBasket.poolId = pool.Id
poolBasket.account = pool.GetPoolAccount()
poolBasket.rewards = sdk.NewCoins()
poolRewardBaskets[weight.PoolId] = poolBasket
}

// Truncate int ensures that there are never more tokens distributed than available
poolReward := sdk.NewCoin(coin.Denom, weight.NormalizedWeight.MulInt(rewards.AmountOf(coin.Denom)).TruncateInt())

// Add reward to pool
accounts.rewards = accounts.rewards.Add(poolReward)
poolBasket.rewards = poolBasket.rewards.Add(poolReward)

// Update map
poolRewards[weight.PoolId] = accounts
poolRewardBaskets[weight.PoolId] = poolBasket
}
}

// Sort PoolRewards for determinism
accountList := make([]PoolRewards, 0)
for _, account := range poolRewards {
accountList := make([]PoolRewardsBasket, 0)
for _, account := range poolRewardBaskets {
accountList = append(accountList, account)
}
sort.Slice(accountList, func(i, j int) bool { return accountList[i].poolId < accountList[j].poolId })

// Redistribute all tokens
for _, account := range accountList {
err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.MultiCoinRewardsRedistributionAccountName, account.account, account.rewards)
if err != nil {
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.MultiCoinRewardsRedistributionAccountName, account.account, account.rewards); err != nil {
return err
}
}
Expand Down
1 change: 0 additions & 1 deletion x/multi_coin_rewards/keeper/logic_distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
TEST CASES - msg_server_leave_pool.go
TODO
* Do not redistribute if no policy is set
* Redistribute single
* Redistribute multi
Expand Down
11 changes: 8 additions & 3 deletions x/multi_coin_rewards/keeper/msg_server_toggle.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ func (k msgServer) ToggleMultiCoinRewards(ctx context.Context, toggle *types.Msg
return nil, err
}

totalRewards := sdk.NewCoins()
if toggle.Enabled {
// User wants to enable multi-coin rewards

Expand All @@ -27,13 +28,11 @@ func (k msgServer) ToggleMultiCoinRewards(ctx context.Context, toggle *types.Msg
}

rewards, _ := k.GetMultiCoinPendingRewardsEntriesByIndex2(sdk.UnwrapSDKContext(ctx), accountAddress.String())
totalRewards := sdk.NewCoins()
for _, reward := range rewards {
totalRewards = totalRewards.Add(reward.Rewards...)
}

err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accountAddress, totalRewards)
if err != nil {
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accountAddress, totalRewards); err != nil {
return nil, err
}

Expand All @@ -52,5 +51,11 @@ func (k msgServer) ToggleMultiCoinRewards(ctx context.Context, toggle *types.Msg
}
}

_ = sdk.UnwrapSDKContext(ctx).EventManager().EmitTypedEvent(&types.EventToggleMultiCoinRewards{
Address: toggle.Creator,
Enabled: toggle.Enabled,
PendingRewardsClaimed: totalRewards.String(),
})

return &types.MsgToggleMultiCoinRewardsResponse{}, nil
}
4 changes: 1 addition & 3 deletions x/multi_coin_rewards/keeper/msg_server_update_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import (
"cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"

// Gov
govTypes "github.com/cosmos/cosmos-sdk/x/gov/types"
// Stakers
"github.com/KYVENetwork/chain/x/multi_coin_rewards/types"
govTypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)

func (k msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) {
Expand Down
39 changes: 39 additions & 0 deletions x/multi_coin_rewards/spec/01_concepts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!--
order: 1
-->

# Concepts

For policy reasons not everybody can or is allowed to receive, hold and
control other tokens. Therefore, users need to opt-in for multi-coin rewards.
Because users could miss the opt-in, there is a grace-period in which multi-coin
rewards can still be retrieved after a claim. If a user does not enable
multi-coin rewards and does not enable it within the grace-period after a claim,
the multi-coin rewards get re-distributed to the existing pools according to
a distribution policy which can be modified by an admin-address which is set
by the governance.

## (Re-)distribution policy

The redistribution policy defines which multi-coin denom gets re-distributed
to which pool depending on a certain weight. To allow quick updates an
admin address (other than the governance) can be specified which can update
the policy. The address can not drain any rewards, but only modify to which
pools the coins get re-distributed to. Therefore, the governance might
set the admin address to a trusted (multi-sig) address.

## Token Flow
Within the withdraw-rewards function inside the CosmosSDK distribution module
the multi-coin-rewards module is called.
1. User has opted in: All tokens are directly paid out to the user
2. User has not opted in: Only the native token is paid out, the other tokens are
transferred to the `multi_coin_rewards` module account. A queue entry is
created and a user has a certain amount of time to enable multi-coin-rewards.
certain amount of time to enable
1. User enables rewards within time: All pending rewards are transferred to the user
2. User does not enable rewards within time: The rewards are transferred to
the `multi_coin_rewards_distribution` module account.

Every 50 blocks all coins in `multi_coin_rewards_distribution` are
redistributed according to the distribution policy. If tokens are not
covered by the policy they remain inside the module account.
47 changes: 47 additions & 0 deletions x/multi_coin_rewards/spec/02_state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<!--
order: 2
-->

# State

The module is mainly responsible for holding the policy itself and keeping
track of who has opted in for multi-coin rewards.

## MultiCoinRewardsEnabled

The users who have opted in for multi-coin rewards are stored as a key in
the IAVL tree. There is no value associated with it. If the key exists,
the users has opted in.

- MultiCoinRewardsEnabled: `0x01 | AccAddress -> {}`

## MultiCoinDistributionPolicy

The MultiCoinDistributionPolicy stores for every denom a list of pools and
weights. The weights determine on how the rewards of a given denom are
re-distributed under the pools.

```protobuf
syntax = "proto3";
// MultiCoinDistributionPolicy ...
message MultiCoinDistributionPolicy {
repeated MultiCoinDistributionDenomEntry entries = 1;
}
// MultiCoinDistributionDenomEntry ...
message MultiCoinDistributionDenomEntry {
string denom = 1;
repeated MultiCoinDistributionPoolWeightEntry pool_weights = 2;
}
// MultiCoinDistributionPoolWeightEntry ...
message MultiCoinDistributionPoolWeightEntry {
uint64 pool_id = 1;
string weight = 2 [
(gogoproto.customtype) = "cosmossdk.io/math.LegacyDec",
(gogoproto.nullable) = false
];
}
```
16 changes: 16 additions & 0 deletions x/multi_coin_rewards/spec/03_messages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!--
order: 4
-->

# Messages

## ToggleMultiCoinRewards

User can enable or disable the retrieval of multi-coin-rewards. If they
enable multi-coin rewards all current pending rewards will be claimed.

## SetMultiCoinRewardDistributionPolicy

Sets the multi coin rewards distribution policy. This can only be done by
the admin address. This can either be the address of the governance itself
or a trusted entity.
9 changes: 9 additions & 0 deletions x/multi_coin_rewards/spec/04_begin_block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!--
order: 4
-->

# BeginBlock

Every 50 blocks all coins in the `multi_coin_rewards_distribution` module account
are re-distributed according to the current policy. If a coin is not covered
by the policy, it remains in the account.
12 changes: 12 additions & 0 deletions x/multi_coin_rewards/spec/05_params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!--
order: 5
-->

# Parameters

The multi-coin-rewards module contains the following parameters:

| Key | Type | Example |
|----------------------------------------------|--------|---------------------------------------------|
| multi_coin_distribution_policy_admin_address | string | kyve10d07y265gmmuvt4z0w9aw880jnsr700jdv7nah |
| multi_coin_distribution_pending_time | uint64 | 1,209,600 |
31 changes: 31 additions & 0 deletions x/multi_coin_rewards/spec/06_events.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
order: 6
-->

# Events

The multi-coin-rewards module contains the following events:

## EventToggleMultiCoinRewards

EventToggleMultiCoinRewards indicates that someone has changed their
multi-coin-settings.

```protobuf
syntax = "proto3";
message EventToggleMultiCoinRewards {
// address ...
string address = 1;
// enabled ...
bool enabled = 2;
// pending_rewards_claimed ...
string pending_rewards_claimed = 3;
}
```

It gets emitted by the following actions:

- SetMultiCoinRewardDistributionPolicy
Loading

0 comments on commit 8d340d2

Please sign in to comment.