Skip to content

Commit

Permalink
feat(vbank): separate smoothing and distribution fraction from epoch
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed May 25, 2022
1 parent 82eeadb commit 9116ede
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 157 deletions.
34 changes: 27 additions & 7 deletions golang/cosmos/proto/agoric/vbank/vbank.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,27 @@ message Params {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;

// fee_epoch_duration_blocks is the length of a fee epoch, in blocks.
// reward_epoch_duration_blocks is the length of a reward epoch, in blocks.
// A value of zero has the same meaning as a value of one:
// the full fee buffer should be distributed immediately.
int64 fee_epoch_duration_blocks = 1 [
(gogoproto.moretags) = "yaml:\"fee_epoch_duration_blocks\""
// the full reward buffer should be distributed immediately.
int64 reward_epoch_duration_blocks = 1 [
(gogoproto.moretags) = "yaml:\"reward_epoch_duration_blocks\""
];

// per_epoch_reward_fraction is a fraction of the reward pool to distrubute
// once every reward epoch. If less than zero, use approximately continuous
// per-block distribution.
string per_epoch_reward_fraction = 2 [
(gogoproto.moretags) = "yaml:\"discrete_epoch_reward_fraction\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// reward_smoothing_blocks is the number of blocks over which to distribute
// an epoch's rewards. If zero, use the same value as
// reward_epoch_duration_blocks.
int64 reward_smoothing_blocks = 3 [
(gogoproto.moretags) = "yaml:\"reward_smoothing_blocks\""
];
}

Expand All @@ -32,16 +48,20 @@ message State {
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];

// reward_rate is the amount of reward, if available, to send to the
// reward_block_amount is the amount of reward, if available, to send to the
// fee collector module on every block.
repeated cosmos.base.v1beta1.Coin reward_rate = 2 [
repeated cosmos.base.v1beta1.Coin reward_block_amount = 2 [
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"reward_rate\"",
(gogoproto.moretags) = "yaml:\"reward_block_amount\"",
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];

// last_sequence is a sequence number for communicating with the VM.
uint64 last_sequence = 3 [
(gogoproto.moretags) = "yaml:\"last_sequence\""
];

int64 last_reward_distribution_block = 4 [
(gogoproto.moretags) = "yaml:\"last_reward_distribution_block\""
];
}
10 changes: 5 additions & 5 deletions golang/cosmos/x/vbank/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ entirely at the ERTP level.
## Parameters

- `feeCollectorName`: the module which handles fee distribution to stakers.
- `fee_epoch_duration_blocks`: the duration (in blocks) over which fees should be given to the fee collector.
- `reward_epoch_duration_blocks`: the duration (in blocks) over which fees should be given to the fee collector.

## State

Expand Down Expand Up @@ -53,19 +53,19 @@ not be bank purses by default.

## Governance

To use Cosmos governance to change the `fee_epoch_duration_blocks` value:
To use Cosmos governance to change the `reward_epoch_duration_blocks` value:

```sh
$ agd query vbank params
fee_epoch_duration_blocks: "720"
reward_epoch_duration_blocks: "720"
$ cat <<EOF > epoch-duration-proposal.json
{
"title": "Vbank param-change test",
"description": "Decrease the fee disbursal epoch parameter to 30 blocks.",
"changes": [
{
"subspace": "vbank",
"key": "fee_epoch_duration_blocks",
"key": "reward_epoch_duration_blocks",
"value": "30"
}
],
Expand All @@ -77,6 +77,6 @@ $ agd tx gov submit-proposal param-change epoch-duration-proposal.json --from=my
$ agd tx vote ...
# After passing,
$ agd query vbank params
fee_epoch_duration_blocks: "30"
reward_epoch_duration_blocks: "30"
$
```
28 changes: 14 additions & 14 deletions golang/cosmos/x/vbank/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ type Keeper struct {
cdc codec.Codec
paramSpace paramtypes.Subspace

accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
feeCollectorName string
PushAction vm.ActionPusher
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
rewardDistributorName string
PushAction vm.ActionPusher
}

// NewKeeper creates a new vbank Keeper instance
func NewKeeper(
cdc codec.Codec, key sdk.StoreKey, paramSpace paramtypes.Subspace,
accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper,
feeCollectorName string,
rewardDistributorName string,
pushAction vm.ActionPusher,
) Keeper {

Expand All @@ -38,13 +38,13 @@ func NewKeeper(
}

return Keeper{
storeKey: key,
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
feeCollectorName: feeCollectorName,
PushAction: pushAction,
storeKey: key,
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: accountKeeper,
bankKeeper: bankKeeper,
rewardDistributorName: rewardDistributorName,
PushAction: pushAction,
}
}

Expand All @@ -60,8 +60,8 @@ func (k Keeper) StoreRewardCoins(ctx sdk.Context, amt sdk.Coins) error {
return k.bankKeeper.MintCoins(ctx, types.ModuleName, amt)
}

func (k Keeper) SendCoinsToFeeCollector(ctx sdk.Context, amt sdk.Coins) error {
return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.feeCollectorName, amt)
func (k Keeper) SendCoinsToRewardDistributor(ctx sdk.Context, amt sdk.Coins) error {
return k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.rewardDistributorName, amt)
}

func (k Keeper) SendCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) error {
Expand Down
81 changes: 81 additions & 0 deletions golang/cosmos/x/vbank/keeper/rewards.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package keeper

import (
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// minCoins returns the minimum of each denomination.
// The input coins should be sorted.
func minCoins(a, b sdk.Coins) sdk.Coins {
min := make([]sdk.Coin, 0)
for indexA, indexB := 0, 0; indexA < len(a) && indexB < len(b); {
coinA, coinB := a[indexA], b[indexB]
switch strings.Compare(coinA.Denom, coinB.Denom) {
case -1: // A < B
indexA++
case 0: // A == B
minCoin := coinA
if coinB.IsLT(minCoin) {
minCoin = coinB
}
if !minCoin.IsZero() {
min = append(min, minCoin)
}
indexA++
indexB++
case 1: // A > B
indexB++
}
}
return sdk.NewCoins(min...)
}

func mulCoins(a sdk.Coins, b sdk.Dec) sdk.Coins {
coins := make([]sdk.Coin, 0, len(a))
for _, coin := range a {
amount := b.MulInt(coin.Amount).TruncateInt()
if amount.IsPositive() {
coins = append(coins, sdk.NewCoin(coin.Denom, amount))
}
}
return sdk.NewCoins(coins...)
}

// DistributeRewards drives the rewards state machine.
func (k Keeper) DistributeRewards(ctx sdk.Context) error {
// Distribute rewards.
state := k.GetState(ctx)
params := k.GetParams(ctx)

smoothingBlocks := params.GetSmoothingBlocks()
thisBlock := ctx.BlockHeight()
cycleIndex := thisBlock - state.LastRewardDistributionBlock

// Check if we're at the end of the last cycle.
if cycleIndex >= params.RewardEpochDurationBlocks {
// Get more rewards to distribute.
toDistribute := mulCoins(state.RewardPool, params.PerEpochRewardFraction)
state.LastRewardDistributionBlock = thisBlock
state.RewardBlockAmount = params.RewardRate(toDistribute, smoothingBlocks)
k.SetState(ctx, state)
}

if cycleIndex >= smoothingBlocks {
// No more distribution to do until the next cycle.
return nil
}

// We're currently within the smoothing period, send the amount to distribute.
xfer := minCoins(state.RewardBlockAmount, state.RewardPool)
if !xfer.IsZero() {
if err := k.SendCoinsToRewardDistributor(ctx, xfer); err != nil {
return err
}
}

state.RewardPool = state.RewardPool.Sub(xfer)
k.SetState(ctx, state)
return nil
}
39 changes: 2 additions & 37 deletions golang/cosmos/x/vbank/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
stdlog "log"
"strings"

"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
Expand All @@ -29,32 +28,6 @@ var (
_ module.AppModuleBasic = AppModuleBasic{}
)

// minCoins returns the minimum of each denomination.
// The input coins should be sorted.
func minCoins(a, b sdk.Coins) sdk.Coins {
min := make([]sdk.Coin, 0)
for indexA, indexB := 0, 0; indexA < len(a) && indexB < len(b); {
coinA, coinB := a[indexA], b[indexB]
switch strings.Compare(coinA.Denom, coinB.Denom) {
case -1: // A < B
indexA++
case 0: // A == B
minCoin := coinA
if coinB.IsLT(minCoin) {
minCoin = coinB
}
if !minCoin.IsZero() {
min = append(min, minCoin)
}
indexA++
indexB++
case 1: // A > B
indexB++
}
}
return sdk.NewCoins(min...)
}

// app module Basics object
type AppModuleBasic struct {
}
Expand Down Expand Up @@ -195,16 +168,8 @@ func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.V

}

// Distribute rewards.
state := am.keeper.GetState(ctx)
xfer := minCoins(state.RewardRate, state.RewardPool)
if !xfer.IsZero() {
err := am.keeper.SendCoinsToFeeCollector(ctx, xfer)
if err != nil {
stdlog.Println("Cannot send rewards", err.Error())
}
state.RewardPool = state.RewardPool.Sub(xfer)
am.keeper.SetState(ctx, state)
if err := am.keeper.DistributeRewards(ctx); err != nil {
stdlog.Println("Cannot distribute rewards", err.Error())
}

return []abci.ValidatorUpdate{}
Expand Down

0 comments on commit 9116ede

Please sign in to comment.