0g-chain/x/cdp/keeper/savings.go
Denali Marsh e2f515ba9e
Querier improvements: CDP and Auction priority 1 queries (#644)
* query auction by lot owner

* add SavingsRateDistributed to store

* v2cdps: filtered cdps query

* update v2cdps cli examples

* add savings rate dist counter to begin blocker

* implement savings rate dist cli query

* implement cdp REST queries

* minor auction CLI/REST updates

* fix auction querier bug

* update REST endpoint to 'cdps'

* update to savings-rate-dist

* update SavingsRateDistributed get/set

* update tests

* fix savings rate dist rounding errors

* 'collateralDenom' -> 'collateralType'

* refactor 'v2cdps' -> 'cdps', add ratio param

* fix augmented CDP type, msg string() method

* fix cdp querier test

* filter query results efficiently

* querier tests

* limit type iteration if owner defined

* improve savings rate dist genesis validation

* default sdk.Dec{} to sdk.ZeroDec in queries

* update condition logic for finding intersection

* fix cdp querier filtering

* Update kava-4 swagger (#653)

* add collateral_type, update cdp params

* savings rate, auctions, get cdps

* drop owner from AuctionResponse

* remove duplicate collateral denom

* update query paths with {collateral-type}
2020-09-16 20:45:10 -04:00

107 lines
4.0 KiB
Go

package keeper
import (
"time"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
"github.com/kava-labs/kava/x/cdp/types"
)
// DistributeSavingsRate distributes surplus that has accumulated in the liquidator account to address holding stable coins according the the savings rate
func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
dp, found := k.GetDebtParam(ctx, debtDenom)
if !found {
return sdkerrors.Wrap(types.ErrDebtNotSupported, debtDenom)
}
savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc)
surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom)
if surplusToDistribute.IsZero() {
return nil
}
modAccountCoins := k.getModuleAccountCoins(ctx, dp.Denom)
totalSupplyLessModAccounts := k.supplyKeeper.GetSupply(ctx).GetTotal().Sub(modAccountCoins)
// values to use in interest calculation
totalSurplus := sdk.NewDecFromInt(surplusToDistribute)
totalSupply := sdk.NewDecFromInt(totalSupplyLessModAccounts.AmountOf(debtDenom))
var iterationErr error
// TODO: avoid iterating over all the accounts by keeping the stored stable coin
// holders' addresses separately.
k.accountKeeper.IterateAccounts(ctx, func(acc authexported.Account) (stop bool) {
_, ok := acc.(supplyexported.ModuleAccountI)
if ok {
// don't distribute savings rate to module accounts
return false
}
debtAmount := acc.GetCoins().AmountOf(debtDenom)
if !debtAmount.IsPositive() {
return false
}
// (balance * rewardToDisribute) / totalSupply
// interest is the ratable fraction of savings rate owed to that account, rounded using bankers rounding
interest := (sdk.NewDecFromInt(debtAmount).Mul(totalSurplus)).Quo(totalSupply).RoundInt()
// sanity check, if we are going to over-distribute due to rounding, distribute only the remaining savings rate that hasn't been distributed.
interest = sdk.MinInt(interest, surplusToDistribute)
// sanity check - don't send saving rate if the rounded amount is zero
if !interest.IsPositive() {
return false
}
// update total savings rate distributed by surplus to distribute
previousSavingsDistributed := k.GetSavingsRateDistributed(ctx)
newTotalDistributed := previousSavingsDistributed.Add(interest)
k.SetSavingsRateDistributed(ctx, newTotalDistributed)
interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest))
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins)
if err != nil {
iterationErr = err
return true
}
surplusToDistribute = surplusToDistribute.Sub(interest)
return false
})
return iterationErr
}
// GetPreviousSavingsDistribution get the time of the previous savings rate distribution
func (k Keeper) GetPreviousSavingsDistribution(ctx sdk.Context) (distTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDistributionTimeKey)
b := store.Get([]byte{})
if b == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &distTime)
return distTime, true
}
// SetPreviousSavingsDistribution set the time of the previous block
func (k Keeper) SetPreviousSavingsDistribution(ctx sdk.Context, distTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDistributionTimeKey)
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(distTime))
}
// getModuleAccountCoins gets the total coin balance of this coin currently held by module accounts
func (k Keeper) getModuleAccountCoins(ctx sdk.Context, denom string) sdk.Coins {
totalModCoinBalance := sdk.NewCoins(sdk.NewCoin(denom, sdk.ZeroInt()))
for macc := range k.maccPerms {
modCoinBalance := k.supplyKeeper.GetModuleAccount(ctx, macc).GetCoins().AmountOf(denom)
if modCoinBalance.IsPositive() {
totalModCoinBalance = totalModCoinBalance.Add(sdk.NewCoin(denom, modCoinBalance))
}
}
return totalModCoinBalance
}