Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add usdx incentives calculation test #765

Merged
merged 4 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
76 changes: 76 additions & 0 deletions x/incentive/keeper/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/incentive"
"github.com/kava-labs/kava/x/pricefeed"
)

Expand Down Expand Up @@ -141,3 +142,78 @@ func NewPricefeedGenStateMulti() app.GenesisState {
}
return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)}
}

func NewIncentiveGenState(previousAccumTime, endTime time.Time, rewardPeriods ...incentive.RewardPeriod) app.GenesisState {
var accumulationTimes incentive.GenesisAccumulationTimes
for _, rp := range rewardPeriods {
accumulationTimes = append(
accumulationTimes,
incentive.NewGenesisAccumulationTime(
rp.CollateralType,
previousAccumTime,
sdk.ZeroDec(),
),
)
}
genesis := incentive.NewGenesisState(
incentive.NewParams(
rewardPeriods,
incentive.Multipliers{
incentive.NewMultiplier(incentive.Small, 1, d("0.25")),
incentive.NewMultiplier(incentive.Large, 12, d("1.0")),
},
endTime,
),
accumulationTimes,
incentive.USDXMintingClaims{},
)
return app.GenesisState{incentive.ModuleName: incentive.ModuleCdc.MustMarshalJSON(genesis)}
}

func NewCDPGenStateHighInterest() app.GenesisState {
oneYear := time.Hour * 24 * 365
cdpGenesis := cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
SurplusAuctionLot: cdp.DefaultSurplusLot,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
DebtAuctionLot: cdp.DefaultDebtLot,
SavingsDistributionFrequency: oneYear * 100, // never run savings distribution
CollateralParams: cdp.CollateralParams{
{
Denom: "bnb",
Type: "bnb-a",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000051034942716"), // 500% APR
LiquidationPenalty: d("0.05"),
AuctionSize: i(50000000000),
Prefix: 0x22,
SpotMarketID: "bnb:usd",
LiquidationMarketID: "bnb:usd",
ConversionFactor: i(8),
},
},
DebtParam: cdp.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
PreviousAccumulationTimes: cdp.GenesisAccumulationTimes{
cdp.NewGenesisAccumulationTime("bnb-a", time.Time{}, sdk.OneDec()),
},
TotalPrincipals: cdp.GenesisTotalPrincipals{
cdp.NewGenesisTotalPrincipal("bnb-a", sdk.ZeroInt()),
},
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
}
9 changes: 7 additions & 2 deletions x/incentive/keeper/rewards.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ func (k Keeper) AccumulateRewards(ctx sdk.Context, rewardPeriod types.RewardPeri
return nil
}
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)
rewardFactor := newRewards.ToDec().Quo(totalPrincipal)
cdpFactor, found := k.cdpKeeper.GetInterestFactor(ctx, rewardPeriod.CollateralType)
if !found {
k.SetPreviousAccrualTime(ctx, rewardPeriod.CollateralType, ctx.BlockTime())
return nil
}
rewardFactor := newRewards.ToDec().Mul(cdpFactor).Quo(totalPrincipal)

previousRewardFactor, found := k.GetRewardFactor(ctx, rewardPeriod.CollateralType)
if !found {
Expand Down Expand Up @@ -110,7 +115,7 @@ func (k Keeper) SynchronizeReward(ctx sdk.Context, cdp cdptypes.CDP) {
return
}
claim.RewardIndexes[index].RewardFactor = globalRewardFactor
newRewardsAmount := rewardsAccumulatedFactor.Mul(cdp.GetTotalPrincipal().Amount.ToDec()).RoundInt()
newRewardsAmount := cdp.GetTotalPrincipal().Amount.ToDec().Quo(cdp.InterestFactor).Mul(rewardsAccumulatedFactor).RoundInt()
if newRewardsAmount.IsZero() {
k.SetClaim(ctx, claim)
return
Expand Down
107 changes: 107 additions & 0 deletions x/incentive/keeper/rewards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ package keeper_test

import (
"fmt"
"testing"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

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

"github.com/kava-labs/kava/app"
cdpkeeper "github.com/kava-labs/kava/x/cdp/keeper"
cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/incentive/types"
)
Expand Down Expand Up @@ -200,6 +203,101 @@ func (suite *KeeperTestSuite) TestSyncRewards() {

}

func TestRewardCalculation(t *testing.T) {

// Test Params
ctype := "bnb-a"
initialTime := time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
rewardsPerSecond := c("ukava", 122_354)
initialCollateral := c("bnb", 10_000_000_000)
initialPrincipal := c("usdx", 100_000_000)
oneYear := time.Hour * 24 * 365
rewardPeriod := types.NewRewardPeriod(
true,
ctype,
initialTime,
initialTime.Add(4*oneYear),
rewardsPerSecond,
)

// Setup app and module params
_, addrs := app.GeneratePrivKeyAddressPairs(5)
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: initialTime})
tApp.InitializeFromGenesisStates(
app.NewAuthGenState(addrs[:1], []sdk.Coins{cs(initialCollateral)}),
NewPricefeedGenStateMulti(),
NewCDPGenStateHighInterest(),
NewIncentiveGenState(initialTime, initialTime.Add(oneYear), rewardPeriod),
)

// Create a CDP
cdpKeeper := tApp.GetCDPKeeper()
err := cdpKeeper.AddCdp(
ctx,
addrs[0],
initialCollateral,
initialPrincipal,
ctype,
)
require.NoError(t, err)

// Calculate expected cdp reward using iteration

// Use 10 blocks, each a very long 630720s, to total 6307200s or 1/5th of a year
// The cdp stability fee is set to the max value 500%, so this time ensures the debt increases a significant amount (doubles)
// High stability fees increase the chance of catching calculation bugs.
blockTimes := newRepeatingSliceInt(630720, 10)
expectedCDPReward := sdk.ZeroDec() //c(rewardPeriod.RewardsPerSecond.Denom, 0)
for _, bt := range blockTimes {
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(time.Duration(int(time.Second) * bt)))

// run cdp and incentive begin blockers to update factors
tApp.BeginBlocker(ctx, abci.RequestBeginBlock{})

// calculate expected cdp reward
cdpBlockReward, err := calculateCDPBlockReward(ctx, cdpKeeper, addrs[0], ctype, sdk.NewInt(int64(bt)), rewardPeriod)
require.NoError(t, err)
expectedCDPReward = expectedCDPReward.Add(cdpBlockReward)
}

// calculate cdp reward using factor
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, addrs[0], ctype)
require.True(t, found)
incentiveKeeper := tApp.GetIncentiveKeeper()
require.NotPanics(t, func() {
incentiveKeeper.SynchronizeReward(ctx, cdp)
})
claim, found := incentiveKeeper.GetClaim(ctx, addrs[0])
require.True(t, found)

// Compare two methods of calculation
relativeError := expectedCDPReward.Sub(claim.Reward.Amount.ToDec()).Quo(expectedCDPReward).Abs()
maxError := d("0.0001")
require.Truef(t, relativeError.LT(maxError),
"percent diff %s > %s , expected: %s, actual %s,", relativeError, maxError, expectedCDPReward, claim.Reward.Amount,
)
}

// calculateCDPBlockReward computes the reward that should be distributed to a cdp for the current block.
func calculateCDPBlockReward(ctx sdk.Context, cdpKeeper cdpkeeper.Keeper, owner sdk.AccAddress, ctype string, timeElapsed sdk.Int, rewardPeriod types.RewardPeriod) (sdk.Dec, error) {
// Calculate total rewards to distribute this block
newRewards := timeElapsed.Mul(rewardPeriod.RewardsPerSecond.Amount)

// Calculate cdp's share of total debt
totalPrincipal := cdpKeeper.GetTotalPrincipal(ctx, ctype, types.PrincipalDenom).ToDec()
// cdpDebt
cdp, found := cdpKeeper.GetCdpByOwnerAndCollateralType(ctx, owner, ctype)
if !found {
return sdk.Dec{}, fmt.Errorf("couldn't find cdp for owner '%s' and collateral type '%s'", owner, ctype)
}
accumulatedInterest := cdpKeeper.CalculateNewInterest(ctx, cdp)
cdpDebt := cdp.Principal.Add(cdp.AccumulatedFees).Add(accumulatedInterest).Amount

// Calculate cdp's reward
return newRewards.Mul(cdpDebt).ToDec().Quo(totalPrincipal), nil
}

func (suite *KeeperTestSuite) SetupWithCDPGenState() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
Expand All @@ -214,3 +312,12 @@ func (suite *KeeperTestSuite) SetupWithCDPGenState() {
suite.keeper = keeper
suite.addrs = addrs
}

// newRepeatingSliceInt creates a slice of the specified length containing a single repeating element.
func newRepeatingSliceInt(element int, length int) []int {
slice := make([]int, length)
for i := 0; i < length; i++ {
slice[i] = element
}
return slice
}
2 changes: 1 addition & 1 deletion x/incentive/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ type SupplyKeeper interface {

// CdpKeeper defines the expected cdp keeper for interacting with cdps
type CdpKeeper interface {
IterateCdpsByCollateralType(ctx sdk.Context, collateralType string, cb func(cdp cdptypes.CDP) (stop bool))
GetTotalPrincipal(ctx sdk.Context, collateralType string, principalDenom string) (total sdk.Int)
GetCdpByOwnerAndCollateralType(ctx sdk.Context, owner sdk.AccAddress, collateralType string) (cdptypes.CDP, bool)
GetInterestFactor(ctx sdk.Context, collateralType string) (sdk.Dec, bool)
}

// AccountKeeper defines the expected keeper interface for interacting with account
Expand Down