diff --git a/migrate/v0_15/incentive.go b/migrate/v0_15/incentive.go index 9dd8ccb7..7e79ff8e 100644 --- a/migrate/v0_15/incentive.go +++ b/migrate/v0_15/incentive.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + v0_15staking "github.com/cosmos/cosmos-sdk/x/staking/types" v0_15hard "github.com/kava-labs/kava/x/hard/types" @@ -16,7 +17,7 @@ import ( ) // Incentive migrates from a v0.14 incentive genesis state to a v0.15 incentive genesis state -func Incentive(cdc *codec.Codec, incentiveGS v0_14incentive.GenesisState, cdps v0_15cdp.CDPs, hardGS v0_15hard.GenesisState) v0_15incentive.GenesisState { +func Incentive(cdc *codec.Codec, incentiveGS v0_14incentive.GenesisState, cdps v0_15cdp.CDPs, hardGS v0_15hard.GenesisState, delegations v0_15staking.Delegations) v0_15incentive.GenesisState { // Migrate params claimMultipliers := v0_15incentive.Multipliers{} for _, m := range incentiveGS.Params.ClaimMultipliers { @@ -117,6 +118,8 @@ func Incentive(cdc *codec.Codec, incentiveGS v0_14incentive.GenesisState, cdps v hardSupplyGenesisRewardState.MultiRewardIndexes, hardBorrowGenesisRewardState.MultiRewardIndexes, ) + delegatorClaims = addMissingDelegatorClaims(delegatorClaims, delegations, delegatorGenesisRewardState.MultiRewardIndexes) + // Add Swap Claims swapClaims := v0_15incentive.DefaultSwapClaims @@ -395,6 +398,36 @@ func getHardClaimByOwner(claims v0_15incentive.HardLiquidityProviderClaims, owne return v0_15incentive.HardLiquidityProviderClaim{}, false } +// getDelegatorClaimByOwner picks out the first delegator claim matching an address. +func getDelegatorClaimByOwner(claims v0_15incentive.DelegatorClaims, owner sdk.AccAddress) (v0_15incentive.DelegatorClaim, bool) { + for _, claim := range claims { + if claim.Owner.Equals(owner) { + return claim, true + } + } + return v0_15incentive.DelegatorClaim{}, false +} + +// addMissingDelegatorClaims checks if there are delegations without claims, and if so creates new claims for them. +func addMissingDelegatorClaims(claims v0_15incentive.DelegatorClaims, delegations v0_15staking.Delegations, globalIndexes v0_15incentive.MultiRewardIndexes) v0_15incentive.DelegatorClaims { + + for _, delegation := range delegations { + + _, found := getDelegatorClaimByOwner(claims, delegation.DelegatorAddress) + if !found { + + claim := v0_15incentive.NewDelegatorClaim( + delegation.DelegatorAddress, + sdk.NewCoins(), + globalIndexes, // indexes are set to current so rewards start accumulating from genesis + ) + claims = append(claims, claim) + + } + } + return claims +} + // getHardDepositByOwner picks out the first deposit matching an address. func getHardDepositByOwner(deposits v0_15hard.Deposits, owner sdk.AccAddress) (v0_15hard.Deposit, bool) { for _, deposit := range deposits { diff --git a/migrate/v0_15/incentive_test.go b/migrate/v0_15/incentive_test.go index dcf88b4e..cbaef24c 100644 --- a/migrate/v0_15/incentive_test.go +++ b/migrate/v0_15/incentive_test.go @@ -4,6 +4,7 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" + v0_15staking "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" v0_15cdp "github.com/kava-labs/kava/x/cdp/types" @@ -525,3 +526,79 @@ func TestAddRewards(t *testing.T) { amendedClaims := addRewards(claims, rewards) require.Equal(t, expectedClaims, amendedClaims) } + +func TestAddMissingDelegatorClaims(t *testing.T) { + claims := v0_15incentive.DelegatorClaims{ + v0_15incentive.NewDelegatorClaim( + sdk.AccAddress("address1"), + nil, + nil, + ), + v0_15incentive.NewDelegatorClaim( + sdk.AccAddress("address3"), + nil, + nil, + ), + } + + delegations := v0_15staking.Delegations{ + { + DelegatorAddress: sdk.AccAddress("address2"), + }, + { + DelegatorAddress: sdk.AccAddress("address1"), + }, + { + DelegatorAddress: sdk.AccAddress("address3"), + }, + { + DelegatorAddress: sdk.AccAddress("address3"), // there can be multiple delegations per delegator + }, + { + DelegatorAddress: sdk.AccAddress("address4"), + }, + { + DelegatorAddress: sdk.AccAddress("address4"), + }, + } + + globalIndexes := v0_15incentive.MultiRewardIndexes{{ + CollateralType: "ukava", + RewardIndexes: v0_15incentive.RewardIndexes{ + { + CollateralType: "hard", + RewardFactor: sdk.MustNewDecFromStr("0.1"), + }, + { + CollateralType: "swp", + RewardFactor: sdk.MustNewDecFromStr("0.2"), + }, + }, + }} + + expectedClaims := v0_15incentive.DelegatorClaims{ + v0_15incentive.NewDelegatorClaim( + sdk.AccAddress("address1"), + nil, + nil, + ), + v0_15incentive.NewDelegatorClaim( + sdk.AccAddress("address3"), + nil, + nil, + ), + v0_15incentive.NewDelegatorClaim( + sdk.AccAddress("address2"), + sdk.NewCoins(), + globalIndexes, + ), + v0_15incentive.NewDelegatorClaim( + sdk.AccAddress("address4"), + sdk.NewCoins(), + globalIndexes, + ), + } + + newClaims := addMissingDelegatorClaims(claims, delegations, globalIndexes) + require.Equal(t, expectedClaims, newClaims) +} diff --git a/migrate/v0_15/migrate.go b/migrate/v0_15/migrate.go index 4cff1a83..719ad1b9 100644 --- a/migrate/v0_15/migrate.go +++ b/migrate/v0_15/migrate.go @@ -9,6 +9,7 @@ import ( authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/auth/vesting" "github.com/cosmos/cosmos-sdk/x/genutil" + v0_15staking "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/cosmos/cosmos-sdk/x/supply" cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" tmtypes "github.com/tendermint/tendermint/types" @@ -79,6 +80,9 @@ func MigrateAppState(v0_14AppState genutil.AppMap) { v0_14Codec.MustUnmarshalJSON(v0_14AppState[v0_14incentive.ModuleName], &incentiveGenState) delete(v0_14AppState, v0_14incentive.ModuleName) + var stakingGenState v0_15staking.GenesisState // staking unchanged between v0_14 and v0_15 + v0_14Codec.MustUnmarshalJSON(v0_14AppState[v0_15staking.ModuleName], &stakingGenState) + var hardGenState v0_15hard.GenesisState // v0_14 hard genesis state is the same as v0_15 v0_15Codec.MustUnmarshalJSON(v0_14AppState[v0_15hard.ModuleName], &hardGenState) @@ -86,7 +90,9 @@ func MigrateAppState(v0_14AppState genutil.AppMap) { 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, hardGenState)) + v0_14AppState[v0_15incentive.ModuleName] = v0_15Codec.MustMarshalJSON( + Incentive(v0_15Codec, incentiveGenState, cdpGenState.CDPs, hardGenState, stakingGenState.Delegations), + ) } // Migrate commmittee app state diff --git a/migrate/v0_15/migrate_test.go b/migrate/v0_15/migrate_test.go index 82a27dd8..ea7c92ee 100644 --- a/migrate/v0_15/migrate_test.go +++ b/migrate/v0_15/migrate_test.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/vesting" "github.com/cosmos/cosmos-sdk/x/genutil" + v0_15staking "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" tmtypes "github.com/tendermint/tendermint/types" @@ -83,10 +84,13 @@ func TestIncentive_Full(t *testing.T) { var oldHardGenState v0_15hard.GenesisState cdc.MustUnmarshalJSON(oldState[v0_15hard.ModuleName], &oldHardGenState) + var oldStakingGenState v0_15staking.GenesisState + cdc.MustUnmarshalJSON(oldState[v0_15staking.ModuleName], &oldStakingGenState) + var oldCDPGenState v0_15cdp.GenesisState cdc.MustUnmarshalJSON(oldState[v0_15cdp.ModuleName], &oldCDPGenState) - newGenState := Incentive(app.MakeCodec(), oldIncentiveGenState, oldCDPGenState.CDPs, oldHardGenState) + newGenState := Incentive(app.MakeCodec(), oldIncentiveGenState, oldCDPGenState.CDPs, oldHardGenState, oldStakingGenState.Delegations) require.NoError(t, newGenState.Validate()) // TODO check params, indexes, and accumulation times @@ -224,8 +228,34 @@ func TestIncentive_Full(t *testing.T) { } } - // 1 new DelegatorClaim should have been created for each existing HardLiquidityProviderClaim - require.Equal(t, len(oldIncentiveGenState.HardLiquidityProviderClaims), len(newGenState.DelegatorClaims)) + // Ensure every delegation has a claim + for _, delegation := range oldStakingGenState.Delegations { + numClaims := 0 + for _, claim := range newGenState.DelegatorClaims { + if delegation.DelegatorAddress.Equals(claim.Owner) { + numClaims++ + } + } + require.Equal(t, 1, numClaims, "delegation '%s' has invalid number of claims '%d'", delegation.DelegatorAddress, numClaims) + } + + // Ensure all delegation indexes are ≤ global values + for _, claim := range newGenState.DelegatorClaims { + for _, ri := range claim.RewardIndexes { + require.Equal(t, v0_15incentive.BondDenom, ri.CollateralType) + + global, found := newGenState.DelegatorRewardState.MultiRewardIndexes.Get(ri.CollateralType) + if !found { + global = v0_15incentive.RewardIndexes{} + } + require.Truef(t, indexesAllLessThanOrEqual(ri.RewardIndexes, global), "invalid delegator claim indexes %s %s", ri.RewardIndexes, global) + } + } + + // Ensure delegator rewards are all initialized at 0 + for _, claim := range newGenState.DelegatorClaims { + require.Equal(t, sdk.NewCoins(), claim.Reward) + } } // collateralTypesMatch checks if the set of coin denoms is equal to the set of CollateralTypes in the indexes. @@ -276,10 +306,13 @@ func TestIncentive_Full_TotalRewards(t *testing.T) { var oldHardGenState v0_15hard.GenesisState cdc.MustUnmarshalJSON(oldState[v0_15hard.ModuleName], &oldHardGenState) + var oldStakingGenState v0_15staking.GenesisState + cdc.MustUnmarshalJSON(oldState[v0_15staking.ModuleName], &oldStakingGenState) + var oldCDPGenState v0_15cdp.GenesisState cdc.MustUnmarshalJSON(oldState[v0_15cdp.ModuleName], &oldCDPGenState) - newGenState := Incentive(app.MakeCodec(), oldIncentiveGenState, oldCDPGenState.CDPs, oldHardGenState) + newGenState := Incentive(app.MakeCodec(), oldIncentiveGenState, oldCDPGenState.CDPs, oldHardGenState, oldStakingGenState.Delegations) // total previous rewards oldTotalRewards := sdk.NewCoins() // total synced unclaimed rewards @@ -333,7 +366,7 @@ func TestIncentive_SwpLPRewards(t *testing.T) { newGenState := v0_15incentive.GenesisState{} require.NotPanics(t, func() { - newGenState = Incentive(app.MakeCodec(), oldIncentiveGenState, v0_15cdp.CDPs{}, v0_15hard.DefaultGenesisState()) + newGenState = Incentive(app.MakeCodec(), oldIncentiveGenState, v0_15cdp.CDPs{}, v0_15hard.DefaultGenesisState(), v0_15staking.Delegations{}) }) err = newGenState.Validate() require.NoError(t, err) @@ -369,7 +402,7 @@ func TestIncentive_SwpDelegatorRewards(t *testing.T) { newGenState := v0_15incentive.GenesisState{} require.NotPanics(t, func() { - newGenState = Incentive(app.MakeCodec(), oldIncentiveGenState, v0_15cdp.CDPs{}, v0_15hard.DefaultGenesisState()) + newGenState = Incentive(app.MakeCodec(), oldIncentiveGenState, v0_15cdp.CDPs{}, v0_15hard.DefaultGenesisState(), v0_15staking.Delegations{}) }) for _, rp := range newGenState.Params.DelegatorRewardPeriods { @@ -389,6 +422,7 @@ func TestIncentive_SwpPoolsValid(t *testing.T) { v0_14incentive.ModuleName: bz, v0_15cdp.ModuleName: app.MakeCodec().MustMarshalJSON(v0_15cdp.DefaultGenesisState()), v0_15hard.ModuleName: app.MakeCodec().MustMarshalJSON(v0_15hard.DefaultGenesisState()), + v0_15staking.ModuleName: app.MakeCodec().MustMarshalJSON(v0_15staking.DefaultGenesisState()), } MigrateAppState(appState)