Skip to content

Commit

Permalink
Implement reward distribution (#25)
Browse files Browse the repository at this point in the history
* replace NBSP with space

* fix core logic

- store only sdk.Coins when storing a reward in kvstore
- fix store methods
- delete a staking when all coins has been unstaked and no rewards left
- emit events in keeper method rather than in msg handler

* revert back parameter order of store methods

* revert back old Reward struct

* rename and simplify Denoms method

* drop legacy amino dependency and replace simd with farmingd

* implement Stakings query endpoint and minor fixes

- rename key getter methods
- unify variable/parameter namings

* fix Unstake logic

* make tx commands work

* change time format

* move plan-related logics into plan.go

* fix Rewards endpoint logic and minor bugs

* use sdk.BigEndianToUint64 instead of encoding/binary

* fix query endpoint bugs

* improve stake/unstake cli commands

* use test suite for testing

* add tests and update sentinel errors

* make logics executed in EndBlock not BeginBlock

* implement epoch-related functions

* implement ProcessQueuedCoins and add test helper

* add and refactor tests

* implement reward distribution logic

also make Create*Plan to return the plan

* apply PR review suggestions

- remove code for debugging purpose
- accumulate reward coins, not overwrite
- only update staking object when there is a change
  • Loading branch information
hallazzang authored Jul 30, 2021
1 parent 5ff55e5 commit e06ecb9
Show file tree
Hide file tree
Showing 18 changed files with 690 additions and 113 deletions.
24 changes: 15 additions & 9 deletions x/farming/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,27 @@ package farming
import (
"time"

abci "github.com/tendermint/tendermint/abci/types"

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

"github.com/tendermint/farming/x/farming/keeper"
"github.com/tendermint/farming/x/farming/types"
)

// BeginBlocker distribute farming rewards for the previous block
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
}

func EndBlocker(ctx sdk.Context, k keeper.Keeper) []abci.ValidatorUpdate {
func EndBlocker(ctx sdk.Context, k keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)
return []abci.ValidatorUpdate{}

lastEpochTime, found := k.GetLastEpochTime(ctx)
if !found {
k.SetLastEpochTime(ctx, ctx.BlockTime())
} else if ctx.BlockTime().Day()-lastEpochTime.Day() > 0 {
if err := k.DistributeRewards(ctx); err != nil {
panic(err)
}
k.ProcessQueuedCoins(ctx)

k.SetLastEpochTime(ctx, ctx.BlockTime())
}

// TODO: implement plan termination logic
}
36 changes: 36 additions & 0 deletions x/farming/keeper/epoch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package keeper

import (
"time"

gogotypes "github.com/gogo/protobuf/types"

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

"github.com/tendermint/farming/x/farming/types"
)

func (k Keeper) GetLastEpochTime(ctx sdk.Context) (time.Time, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GlobalLastEpochTimeKey)
if bz == nil {
return time.Time{}, false
}
var ts gogotypes.Timestamp
k.cdc.MustUnmarshal(bz, &ts)
t, err := gogotypes.TimestampFromProto(&ts)
if err != nil {
panic(err)
}
return t, true
}

func (k Keeper) SetLastEpochTime(ctx sdk.Context, t time.Time) {
store := ctx.KVStore(k.storeKey)
ts, err := gogotypes.TimestampProto(t)
if err != nil {
panic(err)
}
bz := k.cdc.MustMarshal(ts)
store.Set(types.GlobalLastEpochTimeKey, bz)
}
13 changes: 13 additions & 0 deletions x/farming/keeper/epoch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package keeper_test

func (suite *KeeperTestSuite) TestLastEpochTime() {
_, found := suite.keeper.GetLastEpochTime(suite.ctx)
suite.Require().False(found)

t := mustParseRFC3339("2021-07-23T05:01:02Z")
suite.keeper.SetLastEpochTime(suite.ctx, t)

t2, found := suite.keeper.GetLastEpochTime(suite.ctx)
suite.Require().True(found)
suite.Require().Equal(t, t2)
}
2 changes: 1 addition & 1 deletion x/farming/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package keeper
import (
"fmt"

authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/tendermint/tendermint/libs/log"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/tendermint/farming/x/farming/types"
Expand Down
75 changes: 68 additions & 7 deletions x/farming/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,80 @@
package keeper_test

import (
"testing"
"time"

"github.com/stretchr/testify/suite"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

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

"github.com/tendermint/farming/app"
simapp "github.com/tendermint/farming/app"
"github.com/tendermint/farming/x/farming/keeper"
"github.com/tendermint/farming/x/farming/types"
)

// createTestApp returns a farming app with custom FarmingKeeper.
func createTestApp(isCheckTx bool) (*app.FarmingApp, sdk.Context) {
app := app.Setup(isCheckTx)
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{})
app.FarmingKeeper.SetParams(ctx, types.DefaultParams())
const (
denom1 = "denom1"
denom2 = "denom2"
denom3 = "denom3"
)

var (
initialBalances = sdk.NewCoins(
sdk.NewInt64Coin(sdk.DefaultBondDenom, 1_000_000_000),
sdk.NewInt64Coin(denom1, 1_000_000_000),
sdk.NewInt64Coin(denom2, 1_000_000_000),
sdk.NewInt64Coin(denom3, 1_000_000_000))
)

type KeeperTestSuite struct {
suite.Suite

app *simapp.FarmingApp
ctx sdk.Context
keeper keeper.Keeper
addrs []sdk.AccAddress
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

func (suite *KeeperTestSuite) SetupTest() {
app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{})

suite.app = app
suite.ctx = ctx
suite.keeper = suite.app.FarmingKeeper
suite.addrs = simapp.AddTestAddrs(suite.app, suite.ctx, 4, sdk.ZeroInt())
for _, addr := range suite.addrs {
err := simapp.FundAccount(suite.app.BankKeeper, suite.ctx, addr, initialBalances)
suite.Require().NoError(err)
}
}

// Stake is a convenient method to test Keeper.Stake.
func (suite *KeeperTestSuite) Stake(farmer sdk.AccAddress, amt sdk.Coins) types.Staking {
staking, err := suite.keeper.Stake(suite.ctx, farmer, amt)
suite.Require().NoError(err)

return staking
}

func intEq(exp, got sdk.Int) (bool, string, string, string) {
return exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
}

func coinsEq(exp, got sdk.Coins) (bool, string, string, string) {
return exp.IsEqual(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String()
}

return app, ctx
func mustParseRFC3339(s string) time.Time {
t, err := time.Parse(time.RFC3339, s)
if err != nil {
panic(err)
}
return t
}
8 changes: 4 additions & 4 deletions x/farming/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var _ types.MsgServer = msgServer{}
func (k msgServer) CreateFixedAmountPlan(goCtx context.Context, msg *types.MsgCreateFixedAmountPlan) (*types.MsgCreateFixedAmountPlanResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if err := k.Keeper.CreateFixedAmountPlan(ctx, msg, types.PlanTypePrivate); err != nil {
if _, err := k.Keeper.CreateFixedAmountPlan(ctx, msg, types.PlanTypePrivate); err != nil {
return nil, err
}

Expand All @@ -40,7 +40,7 @@ func (k msgServer) CreateFixedAmountPlan(goCtx context.Context, msg *types.MsgCr
func (k msgServer) CreateRatioPlan(goCtx context.Context, msg *types.MsgCreateRatioPlan) (*types.MsgCreateRatioPlanResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if err := k.Keeper.CreateRatioPlan(ctx, msg, types.PlanTypePrivate); err != nil {
if _, err := k.Keeper.CreateRatioPlan(ctx, msg, types.PlanTypePrivate); err != nil {
return nil, err
}

Expand All @@ -51,7 +51,7 @@ func (k msgServer) CreateRatioPlan(goCtx context.Context, msg *types.MsgCreateRa
func (k msgServer) Stake(goCtx context.Context, msg *types.MsgStake) (*types.MsgStakeResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if err := k.Keeper.Stake(ctx, msg.GetFarmer(), msg.StakingCoins); err != nil {
if _, err := k.Keeper.Stake(ctx, msg.GetFarmer(), msg.StakingCoins); err != nil {
return nil, err
}

Expand All @@ -62,7 +62,7 @@ func (k msgServer) Stake(goCtx context.Context, msg *types.MsgStake) (*types.Msg
func (k msgServer) Unstake(goCtx context.Context, msg *types.MsgUnstake) (*types.MsgUnstakeResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)

if err := k.Keeper.Unstake(ctx, msg.GetFarmer(), msg.UnstakingCoins); err != nil {
if _, err := k.Keeper.Unstake(ctx, msg.GetFarmer(), msg.UnstakingCoins); err != nil {
return nil, err
}

Expand Down
24 changes: 12 additions & 12 deletions x/farming/keeper/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,11 @@ func (k Keeper) UnmarshalPlan(bz []byte) (plan types.PlanI, err error) {
}

// CreateFixedAmountPlan sets fixed amount plan.
func (k Keeper) CreateFixedAmountPlan(ctx sdk.Context, msg *types.MsgCreateFixedAmountPlan, typ types.PlanType) error {
func (k Keeper) CreateFixedAmountPlan(ctx sdk.Context, msg *types.MsgCreateFixedAmountPlan, typ types.PlanType) (types.PlanI, error) {
nextId := k.GetNextPlanIDWithUpdate(ctx)
farmingPoolAddrAcc, err := sdk.AccAddressFromBech32(msg.FarmingPoolAddress)
if err != nil {
return err
return nil, err
}
terminationAddrAcc := farmingPoolAddrAcc

Expand All @@ -176,16 +176,16 @@ func (k Keeper) CreateFixedAmountPlan(ctx sdk.Context, msg *types.MsgCreateFixed
balances := k.bankKeeper.GetAllBalances(ctx, farmingPoolAddrAcc)
_, hasNeg := balances.SafeSub(params.PrivatePlanCreationFee)
if hasNeg {
return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "insufficient balance to pay private plan creation fee")
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "insufficient balance to pay private plan creation fee")
}

farmingFeeCollectorAcc, err := sdk.AccAddressFromBech32(params.FarmingFeeCollector)
if err != nil {
return err
return nil, err
}

if err := k.bankKeeper.SendCoins(ctx, farmingPoolAddrAcc, farmingFeeCollectorAcc, params.PrivatePlanCreationFee); err != nil {
return err
return nil, err
}

basePlan := types.NewBasePlan(
Expand Down Expand Up @@ -213,15 +213,15 @@ func (k Keeper) CreateFixedAmountPlan(ctx sdk.Context, msg *types.MsgCreateFixed
),
})

return nil
return fixedPlan, nil
}

// CreateRatioPlan sets ratio plan.
func (k Keeper) CreateRatioPlan(ctx sdk.Context, msg *types.MsgCreateRatioPlan, typ types.PlanType) error {
func (k Keeper) CreateRatioPlan(ctx sdk.Context, msg *types.MsgCreateRatioPlan, typ types.PlanType) (types.PlanI, error) {
nextId := k.GetNextPlanIDWithUpdate(ctx)
farmingPoolAddrAcc, err := sdk.AccAddressFromBech32(msg.FarmingPoolAddress)
if err != nil {
return err
return nil, err
}
terminationAddrAcc := farmingPoolAddrAcc

Expand All @@ -230,16 +230,16 @@ func (k Keeper) CreateRatioPlan(ctx sdk.Context, msg *types.MsgCreateRatioPlan,
balances := k.bankKeeper.GetAllBalances(ctx, farmingPoolAddrAcc)
_, hasNeg := balances.SafeSub(params.PrivatePlanCreationFee)
if hasNeg {
return sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "insufficient balance to pay private plan creation fee")
return nil, sdkerrors.Wrap(sdkerrors.ErrInsufficientFunds, "insufficient balance to pay private plan creation fee")
}

farmingFeeCollectorAcc, err := sdk.AccAddressFromBech32(params.FarmingFeeCollector)
if err != nil {
return err
return nil, err
}

if err := k.bankKeeper.SendCoins(ctx, farmingPoolAddrAcc, farmingFeeCollectorAcc, params.PrivatePlanCreationFee); err != nil {
return err
return nil, err
}

basePlan := types.NewBasePlan(
Expand Down Expand Up @@ -267,5 +267,5 @@ func (k Keeper) CreateRatioPlan(ctx sdk.Context, msg *types.MsgCreateRatioPlan,
),
})

return nil
return ratioPlan, nil
}
55 changes: 25 additions & 30 deletions x/farming/keeper/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,63 +2,58 @@ package keeper_test

import (
"fmt"
"testing"
"time"

"github.com/stretchr/testify/require"

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

"github.com/tendermint/farming/app"
"github.com/tendermint/farming/x/farming/types"
)

func TestGetSetNewPlan(t *testing.T) {
simapp, ctx := createTestApp(true)

farmingPoolAddr := sdk.AccAddress([]byte("farmingPoolAddr"))
terminationAddr := sdk.AccAddress([]byte("terminationAddr"))
func (suite *KeeperTestSuite) TestGetSetNewPlan() {
farmingPoolAddr := sdk.AccAddress("farmingPoolAddr")
terminationAddr := sdk.AccAddress("terminationAddr")
stakingCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1000000)))
coinWeights := sdk.NewDecCoins(
sdk.DecCoin{Denom: "testFarmStakingCoinDenom", Amount: sdk.MustNewDecFromStr("1.0")},
)

addrs := app.AddTestAddrs(simapp, ctx, 2, sdk.NewInt(2000000))
addrs := app.AddTestAddrs(suite.app, suite.ctx, 2, sdk.NewInt(2000000))
farmerAddr := addrs[0]

startTime := time.Now().UTC()
endTime := startTime.AddDate(1, 0, 0)
basePlan := types.NewBasePlan(1, 1, farmingPoolAddr.String(), terminationAddr.String(), coinWeights, startTime, endTime)
fixedPlan := types.NewFixedAmountPlan(basePlan, sdk.NewCoins(sdk.NewCoin("testFarmCoinDenom", sdk.NewInt(1000000))))
simapp.FarmingKeeper.SetPlan(ctx, fixedPlan)
suite.keeper.SetPlan(suite.ctx, fixedPlan)

planGet, found := simapp.FarmingKeeper.GetPlan(ctx, 1)
require.True(t, found)
require.Equal(t, fixedPlan, planGet)
planGet, found := suite.keeper.GetPlan(suite.ctx, 1)
suite.Require().True(found)
suite.Require().Equal(fixedPlan, planGet)

plans := simapp.FarmingKeeper.GetAllPlans(ctx)
require.Len(t, plans, 1)
require.Equal(t, fixedPlan, plans[0])
plans := suite.keeper.GetAllPlans(suite.ctx)
suite.Require().Len(plans, 1)
suite.Require().Equal(fixedPlan, plans[0])

// TODO: tmp test codes for testing functionality, need to separated
err := simapp.FarmingKeeper.Stake(ctx, farmerAddr, stakingCoins)
require.NoError(t, err)
_, err := suite.keeper.Stake(suite.ctx, farmerAddr, stakingCoins)
suite.Require().NoError(err)

stakings := simapp.FarmingKeeper.GetAllStakings(ctx)
stakings := suite.keeper.GetAllStakings(suite.ctx)
fmt.Println(stakings)
stakingByFarmer, found := simapp.FarmingKeeper.GetStakingByFarmer(ctx, farmerAddr)
stakingsByDenom := simapp.FarmingKeeper.GetStakingsByStakingCoinDenom(ctx, sdk.DefaultBondDenom)
stakingByFarmer, found := suite.keeper.GetStakingByFarmer(suite.ctx, farmerAddr)
stakingsByDenom := suite.keeper.GetStakingsByStakingCoinDenom(suite.ctx, sdk.DefaultBondDenom)

require.True(t, found)
require.Equal(t, stakings[0], stakingByFarmer)
require.Equal(t, stakings, stakingsByDenom)
suite.Require().True(found)
suite.Require().Equal(stakings[0], stakingByFarmer)
suite.Require().Equal(stakings, stakingsByDenom)

simapp.FarmingKeeper.SetReward(ctx, sdk.DefaultBondDenom, farmerAddr, stakingCoins)
suite.keeper.SetReward(suite.ctx, sdk.DefaultBondDenom, farmerAddr, stakingCoins)

//rewards := simapp.FarmingKeeper.GetAllRewards(ctx)
//rewardsByFarmer := simapp.FarmingKeeper.GetRewardsByFarmer(ctx, farmerAddr)
//rewardsByDenom := simapp.FarmingKeeper.GetRewardsByStakingCoinDenom(ctx, sdk.DefaultBondDenom)
//rewards := suite.keeper.GetAllRewards(ctx)
//rewardsByFarmer := suite.keeper.GetRewardsByFarmer(ctx, farmerAddr)
//rewardsByDenom := suite.keeper.GetRewardsByStakingCoinDenom(ctx, sdk.DefaultBondDenom)
//
//require.Equal(t, rewards, rewardsByFarmer)
//require.Equal(t, rewards, rewardsByDenom)
//suite.Require().Equal(rewards, rewardsByFarmer)
//suite.Require().Equal(rewards, rewardsByDenom)
}
Loading

0 comments on commit e06ecb9

Please sign in to comment.