package keeper_test import ( "math/big" "testing" sdkmath "cosmossdk.io/math" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/community/keeper" ) func TestStakingRewardsCalculator(t *testing.T) { hugeInflation := new(big.Int).Exp(big.NewInt(2), big.NewInt(205), nil) hugeRewardsPerSec := new(big.Int).Exp(big.NewInt(2), big.NewInt(230), nil) testCases := []struct { name string totalSupply sdkmath.Int totalBonded sdkmath.Int inflation sdkmath.LegacyDec communityTax sdkmath.LegacyDec perSecReward sdkmath.LegacyDec expectedRate sdkmath.LegacyDec }{ { name: "no inflation, no rewards per sec -> 0%", totalSupply: sdkmath.ZeroInt(), totalBonded: sdkmath.ZeroInt(), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyZeroDec(), expectedRate: sdkmath.LegacyZeroDec(), }, // // // inflation-only // // { name: "inflation only: no bonded tokens -> 0%", totalSupply: sdk.NewInt(42), totalBonded: sdkmath.ZeroInt(), inflation: sdkmath.LegacyOneDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyZeroDec(), expectedRate: sdkmath.LegacyZeroDec(), }, { name: "inflation only: 0% inflation -> 0%", totalSupply: sdk.NewInt(123), totalBonded: sdkmath.NewInt(45), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyZeroDec(), expectedRate: sdkmath.LegacyZeroDec(), }, { name: "inflation only: 100% bonded w/ 100% inflation -> 100%", totalSupply: sdk.NewInt(42), totalBonded: sdk.NewInt(42), inflation: sdkmath.LegacyOneDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyZeroDec(), expectedRate: sdkmath.LegacyOneDec(), }, { name: "inflation only: 100% community tax -> 0%", totalSupply: sdk.NewInt(123), totalBonded: sdkmath.NewInt(45), inflation: sdkmath.LegacyMustNewDecFromStr("0.853"), communityTax: sdkmath.LegacyOneDec(), perSecReward: sdkmath.LegacyZeroDec(), expectedRate: sdkmath.LegacyZeroDec(), }, { name: "inflation only: Oct 2023 case", totalSupply: sdk.NewInt(857570000e6), totalBonded: sdk.NewInt(127680000e6), inflation: sdkmath.LegacyMustNewDecFromStr("0.595"), communityTax: sdkmath.LegacyMustNewDecFromStr("0.9495"), perSecReward: sdkmath.LegacyZeroDec(), // expect 20.18% staking reward expectedRate: sdkmath.LegacyMustNewDecFromStr("0.201815746984649122"), // verified manually }, { name: "inflation only: low inflation", totalSupply: sdk.NewInt(857570000e6), totalBonded: sdk.NewInt(127680000e6), inflation: sdkmath.LegacyMustNewDecFromStr("0.0000000001"), communityTax: sdkmath.LegacyMustNewDecFromStr("0.9495"), perSecReward: sdkmath.LegacyZeroDec(), expectedRate: sdkmath.LegacyMustNewDecFromStr("0.000000000033918612"), // verified manually, rounded would be 0.000000000033918613 }, { name: "inflation only: absurdly high inflation", totalSupply: sdk.NewInt(857570000e6), totalBonded: sdk.NewInt(127680000e6), inflation: sdkmath.LegacyNewDecFromBigInt(hugeInflation), // 2^205. a higher exponent than this overflows. communityTax: sdkmath.LegacyMustNewDecFromStr("0.9495"), perSecReward: sdkmath.LegacyZeroDec(), // https://www.wolframalpha.com/input?i=%282%5E205%29+*+%281+-+0.9495%29+*+%28857570000e6+%2F127680000e6%29 expectedRate: sdkmath.LegacyMustNewDecFromStr("17441635052648297161685283657196753398188161373334495592570113.113824561403508771"), // verified manually, would round up }, // // // rewards-only // // { name: "rps only: no bonded tokens -> 0%", totalSupply: sdk.NewInt(42), totalBonded: sdkmath.ZeroInt(), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyMustNewDecFromStr("1234567.123456"), expectedRate: sdkmath.LegacyZeroDec(), }, { name: "rps only: rps = total bonded / seconds in year -> basically 100%", totalSupply: sdk.NewInt(12345), totalBonded: sdkmath.NewInt(1234), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyNewDec(1234).Quo(sdkmath.LegacyNewDec(keeper.SecondsPerYear)), expectedRate: sdkmath.LegacyMustNewDecFromStr("0.999999999999987228"), // <-- for 6-decimal token, this is negligible rounding }, { name: "rps only: 10M kava / year rewards", totalSupply: sdk.NewInt(870950000e6), totalBonded: sdkmath.NewInt(130380000e6), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyMustNewDecFromStr("317097.919837645865043125"), // 10 million kava per year expectedRate: sdkmath.LegacyMustNewDecFromStr("0.076698880196349133"), // verified manually }, { name: "rps only: 25M kava / year rewards", totalSupply: sdk.NewInt(870950000e6), totalBonded: sdkmath.NewInt(130380000e6), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyMustNewDecFromStr("792744.799594114662607813"), // 25 million kava per year expectedRate: sdkmath.LegacyMustNewDecFromStr("0.191747200490872833"), // verified manually }, { name: "rps only: too much kava / year rewards", totalSupply: sdk.NewInt(870950000e6), totalBonded: sdkmath.NewInt(130380000e6), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyNewDecFromBigInt(hugeRewardsPerSec), // 2^230. a higher exponent than this overflows. // https://www.wolframalpha.com/input?i=%28%28365+*+24+*+3600%29+%2F+130380000e6%29+*+%282%5E230%29 expectedRate: sdkmath.LegacyMustNewDecFromStr("417344440850566075319340506352140425426634017001007267992800590.431305795858260469"), // verified manually }, { name: "rps only: low kava / year rewards", totalSupply: sdk.NewInt(870950000e6), totalBonded: sdkmath.NewInt(130380000e6), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyMustNewDecFromStr("0.1"), expectedRate: sdkmath.LegacyMustNewDecFromStr("0.000000024187758858"), // verified manually, rounded would be 0.000000024187758859 }, { name: "rps only: 1 ukava / year rewards", totalSupply: sdk.NewInt(870950000e6), totalBonded: sdkmath.NewInt(130380000e6), inflation: sdkmath.LegacyZeroDec(), communityTax: sdkmath.LegacyZeroDec(), perSecReward: sdkmath.LegacyMustNewDecFromStr("0.000000031709791984"), // 1 ukava per year expectedRate: sdkmath.LegacyMustNewDecFromStr("0.000000000000007669"), // verified manually, rounded would be 0.000000000000007670 }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { rewardRate := keeper.CalculateStakingAnnualPercentage( tc.totalSupply, tc.totalBonded, tc.inflation, tc.communityTax, tc.perSecReward) require.Equal(t, tc.expectedRate, rewardRate) }) } }