0g-chain/migrate/v0_15/usdx_rewards/calculate.go
Ruaridh 22843cd3df
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
2021-08-19 16:20:26 +01:00

109 lines
3.4 KiB
Go

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
}