Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions proto/tendermint/farming/v1beta1/farming.proto
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ message HistoricalRewards {
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];

uint32 reference_count = 2 [(gogoproto.moretags) = "yaml:\"reference_count\""];
}

// OutstandingRewards represents outstanding (un-withdrawn) rewards
Expand Down
4 changes: 2 additions & 2 deletions x/farming/keeper/invariants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func (suite *KeeperTestSuite) TestStakingReservedAmountInvariant() {
func (suite *KeeperTestSuite) TestRemainingRewardsAmountInvariant() {
k, ctx := suite.keeper, suite.ctx

suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
suite.AdvanceEpoch()
Expand Down Expand Up @@ -203,7 +203,7 @@ func (suite *KeeperTestSuite) TestNonNegativeOutstandingRewardsInvariant() {
func (suite *KeeperTestSuite) TestOutstandingRewardsAmountInvariant() {
k, ctx := suite.keeper, suite.ctx

suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
suite.AdvanceEpoch()
Expand Down
26 changes: 13 additions & 13 deletions x/farming/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,15 @@ func (suite *KeeperTestSuite) AdvanceEpoch() {
suite.Require().NoError(err)
}

func (suite *KeeperTestSuite) CreateFixedAmountPlan(farmingPoolAcc sdk.AccAddress, stakingCoinWeightsMap map[string]string, epochAmountMap map[string]int64) {
stakingCoinWeights := sdk.NewDecCoins()
for denom, weight := range stakingCoinWeightsMap {
stakingCoinWeights = stakingCoinWeights.Add(sdk.NewDecCoinFromDec(denom, sdk.MustNewDecFromStr(weight)))
func (suite *KeeperTestSuite) CreateFixedAmountPlan(farmingPoolAcc sdk.AccAddress, stakingCoinWeightsStr, epochAmountStr string) {
stakingCoinWeights, err := sdk.ParseDecCoins(stakingCoinWeightsStr)
if err != nil {
panic(err)
}

epochAmount := sdk.NewCoins()
for denom, amount := range epochAmountMap {
epochAmount = epochAmount.Add(sdk.NewInt64Coin(denom, amount))
epochAmount, err := sdk.ParseCoinsNormalized(epochAmountStr)
if err != nil {
panic(err)
}

msg := types.NewMsgCreateFixedAmountPlan(
Expand All @@ -185,14 +185,14 @@ func (suite *KeeperTestSuite) CreateFixedAmountPlan(farmingPoolAcc sdk.AccAddres
types.ParseTime("9999-12-31T00:00:00Z"),
epochAmount,
)
_, err := suite.keeper.CreateFixedAmountPlan(suite.ctx, msg, farmingPoolAcc, farmingPoolAcc, types.PlanTypePublic)
_, err = suite.keeper.CreateFixedAmountPlan(suite.ctx, msg, farmingPoolAcc, farmingPoolAcc, types.PlanTypePublic)
suite.Require().NoError(err)
}

func (suite *KeeperTestSuite) CreateRatioPlan(farmingPoolAcc sdk.AccAddress, stakingCoinWeightsMap map[string]string, epochRatioStr string) {
stakingCoinWeights := sdk.NewDecCoins()
for denom, weight := range stakingCoinWeightsMap {
stakingCoinWeights = stakingCoinWeights.Add(sdk.NewDecCoinFromDec(denom, sdk.MustNewDecFromStr(weight)))
func (suite *KeeperTestSuite) CreateRatioPlan(farmingPoolAcc sdk.AccAddress, stakingCoinWeightsStr, epochRatioStr string) {
stakingCoinWeights, err := sdk.ParseDecCoins(stakingCoinWeightsStr)
if err != nil {
panic(err)
}

epochRatio := sdk.MustNewDecFromStr(epochRatioStr)
Expand All @@ -205,7 +205,7 @@ func (suite *KeeperTestSuite) CreateRatioPlan(farmingPoolAcc sdk.AccAddress, sta
types.ParseTime("9999-12-31T00:00:00Z"),
epochRatio,
)
_, err := suite.keeper.CreateRatioPlan(suite.ctx, msg, farmingPoolAcc, farmingPoolAcc, types.PlanTypePublic)
_, err = suite.keeper.CreateRatioPlan(suite.ctx, msg, farmingPoolAcc, farmingPoolAcc, types.PlanTypePublic)
suite.Require().NoError(err)
}

Expand Down
10 changes: 5 additions & 5 deletions x/farming/keeper/proposal_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func testModifyPlanRequest(
}

func (suite *KeeperTestSuite) TestModifyPlanRequest() {
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

addr := suite.addrs[5].String()

Expand Down Expand Up @@ -164,7 +164,7 @@ func (suite *KeeperTestSuite) TestModifyPlanRequest() {
}

func (suite *KeeperTestSuite) TestDeletePlanRequest() {
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

proposal := types.NewPublicPlanProposal("title", "description", nil, nil, []types.DeletePlanRequest{{PlanId: 1}})
err := suite.govHandler(suite.ctx, proposal)
Expand All @@ -175,7 +175,7 @@ func (suite *KeeperTestSuite) TestDeletePlanRequest() {
}

func (suite *KeeperTestSuite) TestWithdrawRewardsAfterPlanDeleted() {
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))

Expand Down Expand Up @@ -203,7 +203,7 @@ func (suite *KeeperTestSuite) TestWithdrawRewardsAfterPlanDeleted() {
}

func (suite *KeeperTestSuite) TestWithdrawRewardsAfterPlanTerminated() {
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))

Expand Down Expand Up @@ -240,7 +240,7 @@ func (suite *KeeperTestSuite) TestAccumulatedRewardsAfterPlanModification() {
sdk.NewInt64Coin(denom3, 10000000),
))[0]

suite.CreateRatioPlan(farmingPool, map[string]string{denom1: "1"}, "0.1")
suite.CreateRatioPlan(farmingPool, "1denom1", "0.1")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
suite.AdvanceEpoch()
Expand Down
32 changes: 32 additions & 0 deletions x/farming/keeper/reward.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,33 @@ func (k Keeper) IterateHistoricalRewards(ctx sdk.Context, cb func(stakingCoinDen
}
}

// incrementReferenceCount increments the reference count for a historical rewards.
func (k Keeper) incrementReferenceCount(ctx sdk.Context, stakingCoinDenom string, epoch uint64) {
historical, found := k.GetHistoricalRewards(ctx, stakingCoinDenom, epoch)
if !found {
panic("historical rewards not found")
}
historical.ReferenceCount++
k.SetHistoricalRewards(ctx, stakingCoinDenom, epoch, historical)
}

// decrementReferenceCount decrements the reference count for a historical rewards.
func (k Keeper) decrementReferenceCount(ctx sdk.Context, stakingCoinDenom string, epoch uint64) {
historical, found := k.GetHistoricalRewards(ctx, stakingCoinDenom, epoch)
if !found {
panic("historical rewards not found")
}
if historical.ReferenceCount == 0 {
panic("cannot set negative reference count")
}
historical.ReferenceCount--
if historical.ReferenceCount == 0 {
k.DeleteHistoricalRewards(ctx, stakingCoinDenom, epoch)
} else {
k.SetHistoricalRewards(ctx, stakingCoinDenom, epoch, historical)
}
}

// GetCurrentEpoch returns the current epoch number for a given
// staking coin denom.
func (k Keeper) GetCurrentEpoch(ctx sdk.Context, stakingCoinDenom string) uint64 {
Expand Down Expand Up @@ -226,6 +253,9 @@ func (k Keeper) WithdrawRewards(ctx sdk.Context, farmerAcc sdk.AccAddress, staki
}

currentEpoch := k.GetCurrentEpoch(ctx, stakingCoinDenom)
if currentEpoch == staking.StartingEpoch {
return sdk.Coins{}, nil
}
rewards := k.CalculateRewards(ctx, farmerAcc, stakingCoinDenom, currentEpoch-1)
truncatedRewards, _ := rewards.TruncateDecimal()

Expand All @@ -248,6 +278,7 @@ func (k Keeper) WithdrawRewards(ctx sdk.Context, farmerAcc sdk.AccAddress, staki
k.DecreaseOutstandingRewards(ctx, stakingCoinDenom, rewards)
}

k.decrementReferenceCount(ctx, stakingCoinDenom, staking.StartingEpoch-1)
staking.StartingEpoch = currentEpoch
k.SetStaking(ctx, stakingCoinDenom, farmerAcc, staking)
Comment thread
hallazzang marked this conversation as resolved.

Expand Down Expand Up @@ -463,6 +494,7 @@ func (k Keeper) AllocateRewards(ctx sdk.Context) error {
historical, _ := k.GetHistoricalRewards(ctx, stakingCoinDenom, currentEpoch-1)
k.SetHistoricalRewards(ctx, stakingCoinDenom, currentEpoch, types.HistoricalRewards{
CumulativeUnitRewards: historical.CumulativeUnitRewards.Add(unitRewards...),
ReferenceCount: 1,
})
k.SetCurrentEpoch(ctx, stakingCoinDenom, currentEpoch+1)
}
Expand Down
63 changes: 49 additions & 14 deletions x/farming/keeper/reward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ func (suite *KeeperTestSuite) TestAllocateRewards_FixedAmountPlanAllBalances() {
suite.Require().NoError(err)

// The sum of epoch ratios is exactly 1.
suite.CreateFixedAmountPlan(farmingPoolAcc, map[string]string{denom1: "1"}, map[string]int64{denom3: 600000})
suite.CreateFixedAmountPlan(farmingPoolAcc, map[string]string{denom2: "1"}, map[string]int64{denom3: 400000})
suite.CreateFixedAmountPlan(farmingPoolAcc, "1denom1", "600000denom3")
suite.CreateFixedAmountPlan(farmingPoolAcc, "1denom2", "400000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000), sdk.NewInt64Coin(denom2, 1000000)))

Expand All @@ -197,8 +197,8 @@ func (suite *KeeperTestSuite) TestAllocateRewards_RatioPlanAllBalances() {
suite.Require().NoError(err)

// The sum of epoch ratios is exactly 1.
suite.CreateRatioPlan(farmingPoolAcc, map[string]string{denom1: "1"}, "0.5")
suite.CreateRatioPlan(farmingPoolAcc, map[string]string{denom2: "1"}, "0.5")
suite.CreateRatioPlan(farmingPoolAcc, "1denom1", "0.5")
suite.CreateRatioPlan(farmingPoolAcc, "1denom2", "0.5")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000), sdk.NewInt64Coin(denom2, 1000000)))

Expand All @@ -216,8 +216,8 @@ func (suite *KeeperTestSuite) TestAllocateRewards_FixedAmountPlanOverBalances()

// The sum of epoch amounts is over the balances the farming pool has,
// so the reward allocation should never happen.
suite.CreateFixedAmountPlan(farmingPoolAcc, map[string]string{denom1: "1"}, map[string]int64{denom3: 700000})
suite.CreateFixedAmountPlan(farmingPoolAcc, map[string]string{denom2: "1"}, map[string]int64{denom3: 400000})
suite.CreateFixedAmountPlan(farmingPoolAcc, "1denom1", "700000denom3")
suite.CreateFixedAmountPlan(farmingPoolAcc, "1denom2", "400000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000), sdk.NewInt64Coin(denom2, 1000000)))

Expand All @@ -234,8 +234,8 @@ func (suite *KeeperTestSuite) TestAllocateRewards_RatioPlanOverBalances() {
suite.Require().NoError(err)

// The sum of epoch ratios is over 1, so the reward allocation should never happen.
suite.CreateRatioPlan(farmingPoolAcc, map[string]string{denom1: "1"}, "0.8")
suite.CreateRatioPlan(farmingPoolAcc, map[string]string{denom2: "1"}, "0.5")
suite.CreateRatioPlan(farmingPoolAcc, "1denom1", "0.8")
suite.CreateRatioPlan(farmingPoolAcc, "1denom2", "0.5")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000), sdk.NewInt64Coin(denom2, 1000000)))

Expand All @@ -250,14 +250,14 @@ func (suite *KeeperTestSuite) TestOutstandingRewards() {
// The block time here is not important, and has chosen randomly.
suite.ctx = suite.ctx.WithBlockTime(types.ParseTime("2021-09-01T00:00:00Z"))

suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000denom3")

// Three farmers stake same amount of coins.
suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
suite.Stake(suite.addrs[1], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
suite.Stake(suite.addrs[2], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))

// At first, the outstanding rewards shouldn't exist.
// At first, the outstanding rewards should be zero.
_, found := suite.keeper.GetOutstandingRewards(suite.ctx, denom1)
suite.Require().False(found)

Expand Down Expand Up @@ -307,7 +307,7 @@ func (suite *KeeperTestSuite) TestHarvest() {
}

func (suite *KeeperTestSuite) TestMultipleHarvest() {
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))

Expand All @@ -330,8 +330,8 @@ func (suite *KeeperTestSuite) TestHistoricalRewards() {
suite.ctx = suite.ctx.WithBlockTime(types.ParseTime("2021-08-06T00:00:00Z"))

// Create two plans that share same staking coin denom in their staking coin weights.
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

// Advancing epoch(s) before any staking is made doesn't create any historical rewards records.
suite.AdvanceEpoch()
Expand Down Expand Up @@ -371,7 +371,7 @@ func (suite *KeeperTestSuite) TestHistoricalRewards() {

// Test if initialization and pruning of staking coin info work properly.
func (suite *KeeperTestSuite) TestInitializeAndPruneStakingCoinInfo() {
suite.CreateFixedAmountPlan(suite.addrs[4], map[string]string{denom1: "1"}, map[string]int64{denom3: 1000000})
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

suite.Stake(suite.addrs[0], sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))

Expand Down Expand Up @@ -425,3 +425,38 @@ func (suite *KeeperTestSuite) TestInitializeAndPruneStakingCoinInfo() {
_, found = suite.keeper.GetOutstandingRewards(suite.ctx, denom1)
suite.Require().False(found)
}

func (suite *KeeperTestSuite) TestPruneHistoricalRewards() {
suite.CreateFixedAmountPlan(suite.addrs[4], "1denom1", "1000000denom3")

for i := 0; i < 3; i++ {
suite.AdvanceEpoch()
}

for _, addr := range suite.addrs[:4] {
suite.Stake(addr, sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
}

for i := 0; i < 3; i++ {
suite.AdvanceEpoch()
}

for _, addr := range suite.addrs[:4] {
suite.Stake(addr, sdk.NewCoins(sdk.NewInt64Coin(denom1, 1000000)))
}

for i := 0; i < 3; i++ {
suite.AdvanceEpoch()
}

for _, addr := range suite.addrs[:4] {
suite.Unstake(addr, sdk.NewCoins(sdk.NewInt64Coin(denom1, 2000000)))
}

cnt := 0
suite.keeper.IterateHistoricalRewards(suite.ctx, func(stakingCoinDenom string, epoch uint64, rewards types.HistoricalRewards) (stop bool) {
cnt++
return false
})
suite.Require().Zero(cnt)
}
10 changes: 8 additions & 2 deletions x/farming/keeper/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ func (k Keeper) ReleaseStakingCoins(ctx sdk.Context, farmerAcc sdk.AccAddress, u
// afterStakingCoinAdded is called after a new staking coin denom appeared
// during ProcessQueuedCoins.
func (k Keeper) afterStakingCoinAdded(ctx sdk.Context, stakingCoinDenom string) error {
k.SetHistoricalRewards(ctx, stakingCoinDenom, 0, types.HistoricalRewards{CumulativeUnitRewards: sdk.DecCoins{}})
k.SetHistoricalRewards(ctx, stakingCoinDenom, 0, types.HistoricalRewards{CumulativeUnitRewards: sdk.DecCoins{}, ReferenceCount: 1})
k.SetCurrentEpoch(ctx, stakingCoinDenom, 1)
k.SetOutstandingRewards(ctx, stakingCoinDenom, types.OutstandingRewards{Rewards: sdk.DecCoins{}})
return nil
Expand Down Expand Up @@ -386,10 +386,16 @@ func (k Keeper) ProcessQueuedCoins(ctx sdk.Context) {

k.DeleteQueuedStaking(ctx, stakingCoinDenom, farmerAcc)
k.IncreaseTotalStakings(ctx, stakingCoinDenom, queuedStaking.Amount)

startingEpoch := staking.StartingEpoch
currentEpoch := k.GetCurrentEpoch(ctx, stakingCoinDenom)
k.SetStaking(ctx, stakingCoinDenom, farmerAcc, types.Staking{
Amount: staking.Amount.Add(queuedStaking.Amount),
StartingEpoch: k.GetCurrentEpoch(ctx, stakingCoinDenom),
StartingEpoch: currentEpoch,
})
if startingEpoch != currentEpoch {
k.incrementReferenceCount(ctx, stakingCoinDenom, currentEpoch-1)
}

return false
})
Expand Down
Loading