Incentive migration state fixes: USDX Minting (#998)

* minor migration refactor

* overwrite usdx claims with latest reward indexes

* add missing usdx claims in migration

* add script to calculate missing usdx rewards

* generate missing rewards using preliminary data

* add missing usdx rewards in migration

* update missing rewards data to use correct genesis

* test rewards were not decreased

* add tests for missing reward calculations

* clarify function names

* test to assert total new rewards are as expected

* remove unecessary todo item
This commit is contained in:
Ruaridh 2021-08-19 16:20:26 +01:00 committed by GitHub
parent cac7398120
commit 22843cd3df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 84984 additions and 676 deletions

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
if [ -z "$1" ]
then
echo "Usage: ./generate-test-incentive-data.sh genesis.json"
exit 1
fi
jq < "$1" -c '{"incentive": .app_state.incentive, "cdp": .app_state.cdp }' > testdata/kava-7-test-incentive-state.json

View File

@ -1,14 +1,18 @@
package v0_15
import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
v0_14incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_14"
v0_15incentive "github.com/kava-labs/kava/x/incentive/types"
)
// Incentive migrates from a v0.14 incentive genesis state to a v0.15 incentive genesis state
func Incentive(incentiveGS v0_14incentive.GenesisState) v0_15incentive.GenesisState {
func Incentive(cdc *codec.Codec, incentiveGS v0_14incentive.GenesisState, cdps v0_15cdp.CDPs) v0_15incentive.GenesisState {
// Migrate params
claimMultipliers := v0_15incentive.Multipliers{}
for _, m := range incentiveGS.Params.ClaimMultipliers {
@ -78,12 +82,13 @@ func Incentive(incentiveGS v0_14incentive.GenesisState) v0_15incentive.GenesisSt
swapGenesisRewardState := v0_15incentive.DefaultGenesisRewardState // There is no previous swap rewards so accumulation starts at genesis time.
// Migrate USDX minting claims
usdxMintingClaims := v0_15incentive.USDXMintingClaims{}
for _, claim := range incentiveGS.USDXMintingClaims {
rewardIndexes := migrateRewardIndexes(claim.RewardIndexes)
usdxMintingClaim := v0_15incentive.NewUSDXMintingClaim(claim.Owner, claim.Reward, rewardIndexes)
usdxMintingClaims = append(usdxMintingClaims, usdxMintingClaim)
}
usdxMintingClaims := migrateUSDXMintingClaims(incentiveGS.USDXMintingClaims)
usdxMintingFormattedIndexes := convertRewardIndexesToUSDXMintingIndexes(usdxGenesisRewardState.MultiRewardIndexes)
usdxMintingClaims = replaceUSDXClaimIndexes(usdxMintingClaims, usdxMintingFormattedIndexes)
usdxMintingClaims = ensureAllCDPsHaveClaims(usdxMintingClaims, cdps, usdxMintingFormattedIndexes)
var missedRewards map[string]sdk.Coin
cdc.MustUnmarshalJSON([]byte(missedUSDXMintingRewards), &missedRewards)
usdxMintingClaims = addRewards(usdxMintingClaims, missedRewards)
// Migrate Hard protocol claims (includes creating new Delegator claims)
hardClaims := v0_15incentive.HardLiquidityProviderClaims{}
@ -135,6 +140,83 @@ func Incentive(incentiveGS v0_14incentive.GenesisState) v0_15incentive.GenesisSt
)
}
// migrateUSDXMintingClaims converts the a slice of v0.14 USDX minting claims into v0.15 USDX minting claims.
// As both types are the same underneath, this just converts types and does no other modification.
func migrateUSDXMintingClaims(oldClaims v0_14incentive.USDXMintingClaims) v0_15incentive.USDXMintingClaims {
newClaims := v0_15incentive.USDXMintingClaims{}
for _, oldClaim := range oldClaims {
rewardIndexes := migrateRewardIndexes(oldClaim.RewardIndexes)
usdxMintingClaim := v0_15incentive.NewUSDXMintingClaim(oldClaim.Owner, oldClaim.Reward, rewardIndexes)
newClaims = append(newClaims, usdxMintingClaim)
}
return newClaims
}
// replaceUSDXClaimIndexes overwrites the reward indexes in all the claims with the current global indexes.
func replaceUSDXClaimIndexes(claims v0_15incentive.USDXMintingClaims, globalIndexes v0_15incentive.RewardIndexes) v0_15incentive.USDXMintingClaims {
var amendedClaims v0_15incentive.USDXMintingClaims
for _, claim := range claims {
claim.RewardIndexes = globalIndexes
amendedClaims = append(amendedClaims, claim)
}
return amendedClaims
}
// convertRewardIndexesToUSDXMintingIndexes converts a genesis reward indexes into the format used within usdx minting claims.
func convertRewardIndexesToUSDXMintingIndexes(mris v0_15incentive.MultiRewardIndexes) v0_15incentive.RewardIndexes {
var newIndexes v0_15incentive.RewardIndexes
for _, mri := range mris {
factor, found := mri.RewardIndexes.Get(v0_15incentive.USDXMintingRewardDenom)
if !found {
panic(fmt.Sprintf("found global usdx minting reward index without denom '%s': %s", v0_15incentive.USDXMintingRewardDenom, mri))
}
newIndexes = newIndexes.With(mri.CollateralType, factor)
}
return newIndexes
}
// ensureAllCDPsHaveClaims ensures that there is a claim for every cdp in the provided list.
// It uses the provided global indexes as the indexes for any added claim.
func ensureAllCDPsHaveClaims(newClaims v0_15incentive.USDXMintingClaims, cdps v0_15cdp.CDPs, globalIndexes v0_15incentive.RewardIndexes) v0_15incentive.USDXMintingClaims {
for _, cdp := range cdps {
claimFound := false
for _, claim := range newClaims {
if claim.Owner.Equals(cdp.Owner) {
claimFound = true
break
}
}
if !claimFound {
claim := v0_15incentive.NewUSDXMintingClaim(
cdp.Owner,
sdk.NewCoin(v0_15incentive.USDXMintingRewardDenom, sdk.ZeroInt()),
globalIndexes,
)
newClaims = append(newClaims, claim)
}
}
return newClaims
}
// addRewards adds some coins to a list of claims according to a map of address: coin.
// It panics if any coin denom doesn't match the denom in the claim.
func addRewards(newClaims v0_15incentive.USDXMintingClaims, rewards map[string]sdk.Coin) v0_15incentive.USDXMintingClaims {
var amendedClaims v0_15incentive.USDXMintingClaims
for _, claim := range newClaims {
r, found := rewards[claim.Owner.String()]
if found {
claim.Reward = claim.Reward.Add(r)
}
amendedClaims = append(amendedClaims, claim)
}
return amendedClaims
}
func migrateMultiRewardPeriods(oldPeriods v0_14incentive.MultiRewardPeriods) v0_15incentive.MultiRewardPeriods {
newPeriods := v0_15incentive.MultiRewardPeriods{}
for _, rp := range oldPeriods {

View File

@ -0,0 +1,184 @@
package v0_15
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/incentive"
v0_15incentive "github.com/kava-labs/kava/x/incentive/types"
)
// d parses a string into an sdk.Dec type.
// It is an alias for sdk.MustNewDecFromStr.
var d = sdk.MustNewDecFromStr
func TestReplaceUSDXClaimIndexes(t *testing.T) {
claims := incentive.USDXMintingClaims{
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address1"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 1e9),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.1")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address2"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0")},
{CollateralType: "xrpb-a", RewardFactor: d("0.2")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address3"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{},
),
}
globalIndexes := incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.5")},
{CollateralType: "xrpb-a", RewardFactor: d("0.8")},
}
syncedClaims := replaceUSDXClaimIndexes(claims, globalIndexes)
for i, claim := range syncedClaims {
// check fields are unchanged
require.Equal(t, claim.Owner, claims[i].Owner)
require.Equal(t, claim.Reward, claims[i].Reward)
// except for indexes which have been overwritten
require.Equal(t, globalIndexes, claim.RewardIndexes)
}
}
func TestEnsureAllCDPsHaveClaims(t *testing.T) {
claims := incentive.USDXMintingClaims{
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address1"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 1e9),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.1")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address2"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0")},
{CollateralType: "xrpb-a", RewardFactor: d("0.2")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address3"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{},
),
}
cdps := v0_15cdp.CDPs{
{Owner: sdk.AccAddress("address4")}, // don't need anything more than owner for this test
{Owner: sdk.AccAddress("address1")}, // there can be several cdps of different types with same owner
{Owner: sdk.AccAddress("address1")},
{Owner: sdk.AccAddress("address1")},
{Owner: sdk.AccAddress("address2")},
}
globalIndexes := incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.5")},
{CollateralType: "xrpb-a", RewardFactor: d("0.8")},
}
expectedClaims := incentive.USDXMintingClaims{
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address1"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 1e9),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.1")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address2"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0")},
{CollateralType: "xrpb-a", RewardFactor: d("0.2")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address3"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address4"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.5")},
{CollateralType: "xrpb-a", RewardFactor: d("0.8")},
},
),
}
require.Equal(t, expectedClaims, ensureAllCDPsHaveClaims(claims, cdps, globalIndexes))
}
func TestAddRewards(t *testing.T) {
claims := v0_15incentive.USDXMintingClaims{
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address1"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 1e9),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.1")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address2"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0")},
{CollateralType: "xrpb-a", RewardFactor: d("0.2")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address3"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{},
),
}
rewards := map[string]sdk.Coin{
sdk.AccAddress("address1").String(): sdk.NewInt64Coin(v0_15incentive.USDXMintingRewardDenom, 1e9),
sdk.AccAddress("address3").String(): sdk.NewInt64Coin(v0_15incentive.USDXMintingRewardDenom, 3e9),
sdk.AccAddress("address4").String(): sdk.NewInt64Coin(v0_15incentive.USDXMintingRewardDenom, 1e6),
}
expectedClaims := v0_15incentive.USDXMintingClaims{
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address1"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 2e9),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0.1")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address2"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 0),
incentive.RewardIndexes{
{CollateralType: "bnb-a", RewardFactor: d("0")},
{CollateralType: "xrpb-a", RewardFactor: d("0.2")},
},
),
incentive.NewUSDXMintingClaim(
sdk.AccAddress("address3"),
sdk.NewInt64Coin(incentive.USDXMintingRewardDenom, 3e9),
incentive.RewardIndexes{},
),
}
amendedClaims := addRewards(claims, rewards)
require.Equal(t, expectedClaims, amendedClaims)
}

View File

@ -14,6 +14,7 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
"github.com/kava-labs/kava/app"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
v0_14committee "github.com/kava-labs/kava/x/committee/legacy/v0_14"
v0_15committee "github.com/kava-labs/kava/x/committee/types"
v0_14incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_14"
@ -78,7 +79,12 @@ func MigrateAppState(v0_14AppState genutil.AppMap) {
var incentiveGenState v0_14incentive.GenesisState
v0_14Codec.MustUnmarshalJSON(v0_14AppState[v0_14incentive.ModuleName], &incentiveGenState)
delete(v0_14AppState, v0_14incentive.ModuleName)
v0_14AppState[v0_15incentive.ModuleName] = v0_15Codec.MustMarshalJSON(Incentive(incentiveGenState))
// unmarshal all cdps using v0_15 types as there has been no changes since v0_14
var cdpGenState v0_15cdp.GenesisState
v0_15Codec.MustUnmarshalJSON(v0_14AppState[v0_15cdp.ModuleName], &cdpGenState)
v0_14AppState[v0_15incentive.ModuleName] = v0_15Codec.MustMarshalJSON(Incentive(v0_15Codec, incentiveGenState, cdpGenState.CDPs))
}
// Migrate commmittee app state

View File

@ -13,11 +13,12 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/genutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/kava-labs/kava/app"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
v0_14committee "github.com/kava-labs/kava/x/committee/legacy/v0_14"
v0_15committee "github.com/kava-labs/kava/x/committee/types"
"github.com/kava-labs/kava/x/hard"
@ -63,40 +64,144 @@ func TestCommittee(t *testing.T) {
require.Equal(t, len(oldSPCP.AllowedMarkets), len(newSPCP.AllowedMarkets))
require.Equal(t, len(oldSPCP.AllowedMoneyMarkets), len(newSPCP.AllowedMoneyMarkets))
}
func TestIncentive_MainnetState(t *testing.T) {
// TODO add copy of mainnet state to json
bz, err := ioutil.ReadFile(filepath.Join("testdata", "kava-7-incentive-state.json"))
func TestIncentive_Full(t *testing.T) {
t.Skip() // skip to avoid having to commit a large genesis file to the repo
genDoc, err := tmtypes.GenesisDocFromFile(filepath.Join("testdata", "genesis.json"))
require.NoError(t, err)
cdc := makeV014Codec()
var oldState genutil.AppMap
cdc.MustUnmarshalJSON(genDoc.AppState, &oldState)
var oldIncentiveGenState v0_14incentive.GenesisState
cdc := app.MakeCodec()
require.NotPanics(t, func() {
cdc.MustUnmarshalJSON(bz, &oldIncentiveGenState)
})
cdc.MustUnmarshalJSON(oldState[v0_14incentive.ModuleName], &oldIncentiveGenState)
newGenState := v0_15incentive.GenesisState{}
require.NotPanics(t, func() {
newGenState = Incentive(oldIncentiveGenState)
})
err = newGenState.Validate()
require.NoError(t, err)
var oldCDPGenState v0_15cdp.GenesisState
cdc.MustUnmarshalJSON(oldState[v0_15cdp.ModuleName], &oldCDPGenState)
newGenState := Incentive(app.MakeCodec(), oldIncentiveGenState, oldCDPGenState.CDPs)
require.NoError(t, newGenState.Validate())
// TODO check params, indexes, and accumulation times
// Ensure the usdx claim indexes match global
globalIndexes := newGenState.USDXRewardState.MultiRewardIndexes
for _, claim := range newGenState.USDXMintingClaims {
for _, globalIndex := range globalIndexes {
expectedFactor, found := globalIndex.RewardIndexes.Get(v0_15incentive.USDXMintingRewardDenom)
require.True(t, found)
factor, found := claim.RewardIndexes.Get(globalIndex.CollateralType)
require.True(t, found)
require.Equal(t, expectedFactor, factor)
}
}
// Ensure there is a usdx claim for every cdp
for _, cdp := range oldCDPGenState.CDPs {
numClaims := 0
for _, claim := range newGenState.USDXMintingClaims {
if cdp.Owner.Equals(claim.Owner) {
numClaims++
}
}
require.Equal(t, 1, numClaims, "cdp '%s' has invalid number of claims '%d'", cdp.Owner, numClaims)
// also check cdp indexes are valid
require.True(t, cdp.InterestFactor.GTE(sdk.OneDec()), "found cdp with interest factor < 1")
}
// Check reward amounts
for _, claim := range newGenState.USDXMintingClaims {
// check a few high value accounts
switch claim.Owner.String() {
// check reward is: additional reward + existing unclaimed reward
// note, non zero unclaimed rewards could change if the user submits a claim tx before launch
case "kava1k8lymw58tduy9gm6jkt04ddkjd83nf7sm8xthl":
require.Equal(t, sdk.NewInt(370982556999+0), claim.Reward.Amount)
case "kava1p3ucd3ptpw902fluyjzhq3ffgq4ntddaysyq8h":
require.Equal(t, sdk.NewInt(77550672285+16960713469), claim.Reward.Amount)
case "kava1qe6ahdnhnfugle29054d8uqg7fa44ryx934yc6":
require.Equal(t, sdk.NewInt(40874651319+0), claim.Reward.Amount)
case "kava12h6pq2xqzgtxttrzg7q2rplsyxtv2dc5gwh8rl":
require.Equal(t, sdk.NewInt(30867752254+0), claim.Reward.Amount)
case "kava10hczxv0p3eadcwgt5u79yhahsyuw98u26qan50":
require.Equal(t, sdk.NewInt(22429344254+0), claim.Reward.Amount)
case "kava15wyjwhj6zh79m7adm69pwl3nsq9z8gs9ezs4k7":
require.Equal(t, sdk.NewInt(10252596901+0), claim.Reward.Amount)
case "kava1yg4840l77dfs5zqflldhut27en2mhvvc8vj93x":
require.Equal(t, sdk.NewInt(9898765520+0), claim.Reward.Amount)
case "kava1x242qk6jf2rv23ruvk6fmxp97gg2y75a9r2caq":
require.Equal(t, sdk.NewInt(7761701231+0), claim.Reward.Amount)
case "kava1tstf3u4cw7u4xyu7wxdrnmrpvvmfamq3twcj7f":
require.Equal(t, sdk.NewInt(2466900572+0), claim.Reward.Amount)
}
// check no rewards have been reduced
for _, oldClaim := range oldIncentiveGenState.USDXMintingClaims {
if oldClaim.Owner.Equals(claim.Owner) {
require.Truef(t, claim.Reward.IsGTE(oldClaim.Reward), "found claim with reduced rewards, old %s, new %s", oldClaim, claim)
}
}
}
require.Equal(t, len(oldIncentiveGenState.USDXMintingClaims), len(newGenState.USDXMintingClaims))
require.Equal(t, len(oldIncentiveGenState.HardLiquidityProviderClaims), len(newGenState.HardLiquidityProviderClaims))
// 1 new DelegatorClaim should have been created for each existing HardLiquidityProviderClaim
require.Equal(t, len(oldIncentiveGenState.HardLiquidityProviderClaims), len(newGenState.DelegatorClaims))
}
func TestIncentive(t *testing.T) {
bz, err := ioutil.ReadFile(filepath.Join("testdata", "v0_14-incentive-state.json"))
require.NoError(t, err)
appState := genutil.AppMap{v0_14incentive.ModuleName: bz}
func TestIncentive_Full_TotalRewards(t *testing.T) {
t.Skip() // skip to avoid having to commit a large genesis file to the repo
MigrateAppState(appState)
bz, err = ioutil.ReadFile(filepath.Join("testdata", "v0_15-incentive-state.json"))
genDoc, err := tmtypes.GenesisDocFromFile(filepath.Join("testdata", "genesis.json"))
require.NoError(t, err)
require.JSONEq(t, string(bz), string(appState[v0_15incentive.ModuleName]))
cdc := makeV014Codec()
var oldState genutil.AppMap
cdc.MustUnmarshalJSON(genDoc.AppState, &oldState)
var oldIncentiveGenState v0_14incentive.GenesisState
cdc.MustUnmarshalJSON(oldState[v0_14incentive.ModuleName], &oldIncentiveGenState)
var oldCDPGenState v0_15cdp.GenesisState
cdc.MustUnmarshalJSON(oldState[v0_15cdp.ModuleName], &oldCDPGenState)
newGenState := Incentive(app.MakeCodec(), oldIncentiveGenState, oldCDPGenState.CDPs)
// total previous rewards
oldTotalRewards := sdk.NewCoins() // total synced unclaimed rewards
for _, claim := range oldIncentiveGenState.HardLiquidityProviderClaims {
oldTotalRewards = oldTotalRewards.Add(claim.Reward...)
}
for _, claim := range oldIncentiveGenState.USDXMintingClaims {
oldTotalRewards = oldTotalRewards.Add(claim.Reward)
}
// total new rewards
newTotalRewards := sdk.NewCoins() // total synced unclaimed rewards
for _, claim := range newGenState.USDXMintingClaims {
newTotalRewards = newTotalRewards.Add(claim.Reward)
}
for _, claim := range newGenState.HardLiquidityProviderClaims {
newTotalRewards = newTotalRewards.Add(claim.Reward...)
}
// rewards added in migration
additionalRewards := sdk.NewCoins()
var missedRewards map[string]sdk.Coin
cdc.MustUnmarshalJSON([]byte(missedUSDXMintingRewards), &missedRewards)
for _, c := range missedRewards {
additionalRewards = additionalRewards.Add(c)
}
require.Equal(t, oldTotalRewards.Add(additionalRewards...), newTotalRewards)
}
func TestSwap(t *testing.T) {

5
migrate/v0_15/rewards.go Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,277 +0,0 @@
{
"hard_borrow_accumulation_times": [],
"hard_borrow_reward_indexes": [],
"hard_delegator_accumulation_times": [
{
"collateral_type": "ukava",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
}
],
"hard_delegator_reward_indexes": [
{
"collateral_type": "ukava",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
}
],
"hard_liquidity_provider_claims": [
{
"base_claim": {
"owner": "kava1zx2gfs37vszrgrxke22mtlfj6hpl72y03ur7xr",
"reward": [
{
"amount": "48396109",
"denom": "hard"
},
{
"amount": "1",
"denom": "ukava"
}
]
},
"borrow_reward_indexes": [],
"delegator_reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
],
"supply_reward_indexes": [
{
"collateral_type": "bnb",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
},
{
"collateral_type": "usdx",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
},
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
}
]
},
{
"base_claim": {
"owner": "kava1zyu77sruwsk7vw8pkcrj20ees0gyjlfntrgahy",
"reward": []
},
"borrow_reward_indexes": [],
"delegator_reward_indexes": [],
"supply_reward_indexes": []
},
{
"base_claim": {
"owner": "kava1zyu7gxmdcd7x83ndl2nvegj57lasf7cel4nrex",
"reward": [
{
"amount": "6442528",
"denom": "hard"
}
]
},
"borrow_reward_indexes": [],
"delegator_reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
],
"supply_reward_indexes": []
}
],
"hard_supply_accumulation_times": [
{
"collateral_type": "bnb",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
},
{
"collateral_type": "usdx",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
}
],
"hard_supply_reward_indexes": [
{
"collateral_type": "bnb",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
},
{
"collateral_type": "usdx",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
},
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
}
],
"params": {
"claim_end": "2026-04-08T14:00:00Z",
"claim_multipliers": [
{
"factor": "0.200000000000000000",
"months_lockup": "1",
"name": "small"
},
{
"factor": "1.000000000000000000",
"months_lockup": "12",
"name": "large"
}
],
"hard_borrow_reward_periods": [],
"hard_delegator_reward_periods": [
{
"active": true,
"collateral_type": "ukava",
"end": "2024-10-16T14:00:00Z",
"rewards_per_second": {
"amount": "633761",
"denom": "hard"
},
"start": "2020-10-16T14:00:00Z"
}
],
"hard_supply_reward_periods": [
{
"active": true,
"collateral_type": "usdx",
"end": "2024-10-16T14:00:00Z",
"rewards_per_second": [
{
"amount": "373919",
"denom": "hard"
},
{
"amount": "373919",
"denom": "ukava"
}
],
"start": "2020-10-16T14:00:00Z"
},
{
"active": true,
"collateral_type": "bnb",
"end": "2024-10-16T14:00:00Z",
"rewards_per_second": [
{
"amount": "12675",
"denom": "hard"
}
],
"start": "2020-10-16T14:00:00Z"
}
],
"usdx_minting_reward_periods": [
{
"active": true,
"collateral_type": "btcb-a",
"end": "2022-04-08T14:00:00Z",
"rewards_per_second": {
"amount": "110780",
"denom": "ukava"
},
"start": "2021-04-08T15:00:00Z"
},
{
"active": true,
"collateral_type": "bnb-a",
"end": "2022-04-08T14:00:00Z",
"rewards_per_second": {
"amount": "122354",
"denom": "ukava"
},
"start": "2021-04-08T15:00:00Z"
}
]
},
"usdx_accumulation_times": [
{
"collateral_type": "bnb-a",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
},
{
"collateral_type": "btcb-a",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
}
],
"usdx_minting_claims": [
{
"base_claim": {
"owner": "kava177dtrf8ahjp3wud6p3aqurpuyqae3mq8n069m9",
"reward": {
"amount": "81528",
"denom": "ukava"
}
},
"reward_indexes": [
{
"collateral_type": "bnb-a",
"reward_factor": "0.000000000000000000"
}
]
},
{
"base_claim": {
"owner": "kava18dde5uvs9h474wj5pe69wg66s2v30whpsqgwee",
"reward": {
"amount": "4825232",
"denom": "ukava"
}
},
"reward_indexes": [
{
"collateral_type": "bnb-a",
"reward_factor": "0.000000000000000000"
},
{
"collateral_type": "btcb-a",
"reward_factor": "0.000000000000000000"
}
]
}
],
"usdx_reward_indexes": [
{
"collateral_type": "bnb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
},
{
"collateral_type": "btcb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
}
]
}

View File

@ -1,367 +0,0 @@
{
"delegator_claims": [
{
"base_claim": {
"owner": "kava1zx2gfs37vszrgrxke22mtlfj6hpl72y03ur7xr",
"reward": []
},
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
}
]
},
{
"base_claim": {
"owner": "kava1zyu77sruwsk7vw8pkcrj20ees0gyjlfntrgahy",
"reward": []
},
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_indexes": []
}
]
},
{
"base_claim": {
"owner": "kava1zyu7gxmdcd7x83ndl2nvegj57lasf7cel4nrex",
"reward": []
},
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
}
]
}
],
"delegator_reward_state": {
"accumulation_times": [
{
"collateral_type": "ukava",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
}
],
"multi_reward_indexes": [
{
"collateral_type": "ukava",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
}
]
},
"hard_borrow_reward_state": {
"accumulation_times": [],
"multi_reward_indexes": []
},
"hard_liquidity_provider_claims": [
{
"base_claim": {
"owner": "kava1zx2gfs37vszrgrxke22mtlfj6hpl72y03ur7xr",
"reward": [
{
"amount": "48396109",
"denom": "hard"
},
{
"amount": "1",
"denom": "ukava"
}
]
},
"borrow_reward_indexes": [],
"supply_reward_indexes": [
{
"collateral_type": "bnb",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
},
{
"collateral_type": "usdx",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
},
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
}
]
},
{
"base_claim": {
"owner": "kava1zyu77sruwsk7vw8pkcrj20ees0gyjlfntrgahy",
"reward": []
},
"borrow_reward_indexes": [],
"supply_reward_indexes": []
},
{
"base_claim": {
"owner": "kava1zyu7gxmdcd7x83ndl2nvegj57lasf7cel4nrex",
"reward": [
{
"amount": "6442528",
"denom": "hard"
}
]
},
"borrow_reward_indexes": [],
"supply_reward_indexes": []
}
],
"hard_supply_reward_state": {
"accumulation_times": [
{
"collateral_type": "bnb",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
},
{
"collateral_type": "usdx",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
}
],
"multi_reward_indexes": [
{
"collateral_type": "bnb",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
}
]
},
{
"collateral_type": "usdx",
"reward_indexes": [
{
"collateral_type": "hard",
"reward_factor": "0.000000000000000000"
},
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
}
]
},
"params": {
"claim_end": "2026-04-08T14:00:00Z",
"claim_multipliers": [
{
"denom": "hard",
"multipliers": [
{
"name": "small",
"months_lockup": "1",
"factor": "0.200000000000000000"
},
{
"name": "large",
"months_lockup": "12",
"factor": "1.000000000000000000"
}
]
},
{
"denom": "ukava",
"multipliers": [
{
"name": "small",
"months_lockup": "1",
"factor": "0.200000000000000000"
},
{
"name": "large",
"months_lockup": "12",
"factor": "1.000000000000000000"
}
]
},
{
"denom": "swp",
"multipliers": [
{
"name": "small",
"months_lockup": "1",
"factor": "0.100000000000000000"
},
{
"name": "large",
"months_lockup": "12",
"factor": "1.000000000000000000"
}
]
}
],
"delegator_reward_periods": [
{
"active": true,
"collateral_type": "ukava",
"end": "2024-10-16T14:00:00Z",
"rewards_per_second": [
{
"amount": "633761",
"denom": "hard"
},
{
"amount": "1",
"denom": "swp"
}
],
"start": "2020-10-16T14:00:00Z"
}
],
"hard_borrow_reward_periods": [],
"hard_supply_reward_periods": [
{
"active": true,
"collateral_type": "usdx",
"end": "2024-10-16T14:00:00Z",
"rewards_per_second": [
{
"amount": "373919",
"denom": "hard"
},
{
"amount": "373919",
"denom": "ukava"
}
],
"start": "2020-10-16T14:00:00Z"
},
{
"active": true,
"collateral_type": "bnb",
"end": "2024-10-16T14:00:00Z",
"rewards_per_second": [
{
"amount": "12675",
"denom": "hard"
}
],
"start": "2020-10-16T14:00:00Z"
}
],
"swap_reward_periods": [],
"usdx_minting_reward_periods": [
{
"active": true,
"collateral_type": "btcb-a",
"end": "2022-04-08T14:00:00Z",
"rewards_per_second": {
"amount": "110780",
"denom": "ukava"
},
"start": "2021-04-08T15:00:00Z"
},
{
"active": true,
"collateral_type": "bnb-a",
"end": "2022-04-08T14:00:00Z",
"rewards_per_second": {
"amount": "122354",
"denom": "ukava"
},
"start": "2021-04-08T15:00:00Z"
}
]
},
"swap_claims": [],
"swap_reward_state": {
"accumulation_times": [],
"multi_reward_indexes": []
},
"usdx_minting_claims": [
{
"base_claim": {
"owner": "kava177dtrf8ahjp3wud6p3aqurpuyqae3mq8n069m9",
"reward": {
"amount": "81528",
"denom": "ukava"
}
},
"reward_indexes": [
{
"collateral_type": "bnb-a",
"reward_factor": "0.000000000000000000"
}
]
},
{
"base_claim": {
"owner": "kava18dde5uvs9h474wj5pe69wg66s2v30whpsqgwee",
"reward": {
"amount": "4825232",
"denom": "ukava"
}
},
"reward_indexes": [
{
"collateral_type": "bnb-a",
"reward_factor": "0.000000000000000000"
},
{
"collateral_type": "btcb-a",
"reward_factor": "0.000000000000000000"
}
]
}
],
"usdx_reward_state": {
"accumulation_times": [
{
"collateral_type": "bnb-a",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
},
{
"collateral_type": "btcb-a",
"previous_accumulation_time": "2021-04-08T15:00:00Z"
}
],
"multi_reward_indexes": [
{
"collateral_type": "bnb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
},
{
"collateral_type": "btcb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.000000000000000000"
}
]
}
]
}
}

View File

@ -0,0 +1,108 @@
package main
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
v0_15incentive "github.com/kava-labs/kava/x/incentive"
)
type rewards map[string]sdk.Coin
type rewardsCalculator struct {
claims v0_15incentive.USDXMintingClaims
globalIndexes v0_15incentive.RewardIndexes
cdps v0_15cdp.CDPs
}
func NewRewardsCalculator(claims v0_15incentive.USDXMintingClaims, globalIndexes v0_15incentive.RewardIndexes, cdps v0_15cdp.CDPs) rewardsCalculator {
return rewardsCalculator{
claims: claims,
globalIndexes: globalIndexes,
cdps: cdps,
}
}
// Calculate synchronizes all of the claims and returns the new rewards to be added by owner address.
func (rc rewardsCalculator) Calculate() (rewards, error) {
rewards := rewards{}
for _, claim := range rc.claims {
reward, err := rc.calculateRewardsForClaim(claim)
if err != nil {
return nil, err
}
if _, found := rewards[claim.Owner.String()]; found {
return nil, fmt.Errorf("duplicate claim found: %s", claim.Owner)
}
if !reward.IsZero() {
rewards[claim.Owner.String()] = reward
}
}
return rewards, nil
}
// calculateRewardsForClaim synchronizes the claim to and returns the new rewards that should be added.
// It syncs against the stored global indexes, looking up cdps for the source share amounts.
func (rc rewardsCalculator) calculateRewardsForClaim(claim v0_15incentive.USDXMintingClaim) (sdk.Coin, error) {
reward := sdk.ZeroInt()
for _, index := range rc.globalIndexes {
oldFactor, found := claim.RewardIndexes.Get(index.CollateralType)
if !found {
oldFactor = sdk.ZeroDec()
}
sourceShares := sdk.ZeroDec()
cdp, found := rc.getCDP(claim.Owner, index.CollateralType)
if found {
sourceShares = cdp.GetTotalPrincipal().Amount.ToDec()
}
amount, err := rc.calculateSingleReward(oldFactor, index.RewardFactor, sourceShares)
if err != nil {
return sdk.Coin{}, err
}
reward = reward.Add(amount)
}
return sdk.NewCoin(v0_15incentive.USDXMintingRewardDenom, reward), nil
}
// calculateSingleReward computes how much rewards should have accrued to a reward source (eg a user's btcb-a cdp principal)
// between two index values.
func (rc rewardsCalculator) calculateSingleReward(oldIndex, newIndex, sourceShares sdk.Dec) (sdk.Int, error) {
increase := newIndex.Sub(oldIndex)
if increase.IsNegative() {
return sdk.Int{}, sdkerrors.Wrapf(v0_15incentive.ErrDecreasingRewardFactor, "old: %v, new: %v", oldIndex, newIndex)
}
reward := increase.Mul(sourceShares).RoundInt()
return reward, nil
}
// getCDP looks up a cdp by owner and collateral type.
func (rc rewardsCalculator) getCDP(owner sdk.AccAddress, collateralType string) (v0_15cdp.CDP, bool) {
for _, cdp := range rc.cdps {
if cdp.Owner.Equals(owner) && cdp.Type == collateralType {
return cdp, true
}
}
return v0_15cdp.CDP{}, false
}
func convertRewardIndexesToUSDXMintingIndexes(mris v0_15incentive.MultiRewardIndexes) v0_15incentive.RewardIndexes {
var newIndexes v0_15incentive.RewardIndexes
for _, mri := range mris {
factor, found := mri.RewardIndexes.Get(v0_15incentive.USDXMintingRewardDenom)
if !found {
panic(fmt.Sprintf("found global usdx minting reward index without denom '%s': %s", v0_15incentive.USDXMintingRewardDenom, mri))
}
newIndexes = newIndexes.With(mri.CollateralType, factor)
}
return newIndexes
}

View File

@ -0,0 +1,124 @@
package main
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
v0_15incentive "github.com/kava-labs/kava/x/incentive/types"
)
func TestCalculateRewardsForClaim(t *testing.T) {
type expect struct {
amount sdk.Int
err bool
}
testCases := []struct {
name string
claimIndexes v0_15incentive.RewardIndexes
globalIndexes v0_15incentive.RewardIndexes
cdps v0_15cdp.CDPs
expected expect
}{
{
name: "single cdp is synced",
claimIndexes: v0_15incentive.RewardIndexes{
{
CollateralType: "busd-a",
RewardFactor: sdk.MustNewDecFromStr("0.1"),
},
},
globalIndexes: v0_15incentive.RewardIndexes{
{
CollateralType: "busd-a",
RewardFactor: sdk.MustNewDecFromStr("0.2"),
},
},
cdps: v0_15cdp.CDPs{{
Owner: sdk.AccAddress("address1"),
Type: "busd-a",
Principal: sdk.NewInt64Coin("usdx", 1000),
AccumulatedFees: sdk.NewInt64Coin("usdx", 10),
}},
expected: expect{amount: sdk.NewInt(101)},
},
{
name: "missing claim index is assumed 0",
claimIndexes: v0_15incentive.RewardIndexes{},
globalIndexes: v0_15incentive.RewardIndexes{
{
CollateralType: "busd-a",
RewardFactor: sdk.MustNewDecFromStr("0.2"),
},
},
cdps: v0_15cdp.CDPs{{
Owner: sdk.AccAddress("address1"),
Type: "busd-a",
Principal: sdk.NewInt64Coin("usdx", 1000),
AccumulatedFees: sdk.NewInt64Coin("usdx", 10),
}},
expected: expect{amount: sdk.NewInt(202)},
},
{
name: "multiple cdps are synced",
claimIndexes: v0_15incentive.RewardIndexes{
{
CollateralType: "busd-a",
RewardFactor: sdk.MustNewDecFromStr("0.1"),
},
{
CollateralType: "xrpb-a",
RewardFactor: sdk.MustNewDecFromStr("1"),
},
},
globalIndexes: v0_15incentive.RewardIndexes{
{
CollateralType: "busd-a",
RewardFactor: sdk.MustNewDecFromStr("0.2"),
},
{
CollateralType: "xrpb-a",
RewardFactor: sdk.MustNewDecFromStr("2"),
},
},
cdps: v0_15cdp.CDPs{
{
Owner: sdk.AccAddress("address1"),
Type: "busd-a",
Principal: sdk.NewInt64Coin("usdx", 1000),
AccumulatedFees: sdk.NewInt64Coin("usdx", 100),
},
{
Owner: sdk.AccAddress("address1"),
Type: "xrpb-a",
Principal: sdk.NewInt64Coin("usdx", 10),
AccumulatedFees: sdk.NewInt64Coin("usdx", 1),
}},
expected: expect{amount: sdk.NewInt(110 + 11)},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rc := rewardsCalculator{
globalIndexes: tc.globalIndexes,
cdps: tc.cdps,
}
reward, err := rc.calculateRewardsForClaim(v0_15incentive.NewUSDXMintingClaim(
sdk.AccAddress("address1"),
sdk.NewInt64Coin("ukava", 0),
tc.claimIndexes,
))
if tc.expected.err {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, v0_15incentive.USDXMintingRewardDenom, reward.Denom)
require.Truef(t, tc.expected.amount.Equal(reward.Amount), "amount not equal %s, %s", tc.expected.amount, reward.Amount)
}
})
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,65 @@
[
{
"collateral_type": "bnb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.043949244534927716"
}
]
},
{
"collateral_type": "btcb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.046551281526135881"
}
]
},
{
"collateral_type": "busd-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.011468527653896411"
}
]
},
{
"collateral_type": "hard-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.123365881475003544"
}
]
},
{
"collateral_type": "hbtc-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "1.472331865900201761"
}
]
},
{
"collateral_type": "ukava-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.038864939729274249"
}
]
},
{
"collateral_type": "xrpb-a",
"reward_indexes": [
{
"collateral_type": "ukava",
"reward_factor": "0.020676676769949043"
}
]
}
]

View File

@ -0,0 +1,87 @@
// package main calculates missing rewards from kava-7 so they can be include in the migration to kava-8.
// In the migration folder run `go run ./usdx_rewards` to compute the output file. The output is a go file so it can be included in the migrations.
// Data is taken from an export at height 829296 of kava-7 using v0.14.3.
package main
import (
"io/ioutil"
"log"
"path/filepath"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
v0_15cdp "github.com/kava-labs/kava/x/cdp/types"
v0_15incentive "github.com/kava-labs/kava/x/incentive"
)
var (
outFileName = "rewards.go"
dirPath = "./usdx_rewards"
cdpsFileName = filepath.Join(dirPath, "cdp-cdps-829296.json")
indexesFileName = filepath.Join(dirPath, "incentive-usdx-indexes-829296.json")
claimsFileName = filepath.Join(dirPath, "incentive-usdx-claims-829296.json")
)
func main() {
app.SetBech32AddressPrefixes(sdk.GetConfig())
cdc := app.MakeCodec()
var cdps v0_15cdp.CDPs
if err := fetchFromJSONFile(cdc, cdpsFileName, &cdps); err != nil {
log.Fatal(err.Error())
}
var globalIndexes v0_15incentive.MultiRewardIndexes
if err := fetchFromJSONFile(cdc, indexesFileName, &globalIndexes); err != nil {
log.Fatal(err.Error())
}
var claims v0_15incentive.USDXMintingClaims
if err := fetchFromJSONFile(cdc, claimsFileName, &claims); err != nil {
log.Fatal(err.Error())
}
calculator := NewRewardsCalculator(claims, convertRewardIndexesToUSDXMintingIndexes(globalIndexes), cdps)
rewards, err := calculator.Calculate()
if err != nil {
log.Fatal(err.Error())
}
out := createOutputString(cdc, rewards)
if err = ioutil.WriteFile(outFileName, []byte(out), 0644); err != nil {
log.Fatal(err.Error())
}
}
// fetchFromJSONFile reads and unmarshals the contents of a json file.
func fetchFromJSONFile(cdc *codec.Codec, filePath string, pointer interface{}) error {
bz, err := ioutil.ReadFile(filePath)
if err != nil {
return err
}
if err = cdc.UnmarshalJSON(bz, pointer); err != nil {
return err
}
return nil
}
func createOutputString(cdc *codec.Codec, rewards rewards) string {
bz := cdc.MustMarshalJSON(rewards)
// sort json keys so make changes easier to compare
bz = sdk.MustSortJSON(bz)
out := `// Code generated by package ./usdx_rewards DO NOT EDIT.
package v0_15
var missedUSDXMintingRewards = ` + "`" + string(bz) + "`\n"
return out
}