[R4R] Update fees for all cdps (#449)

* update cdp fees in begin block

Co-authored-by: Federico Kunze <federico.kunze94@gmail.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Denali Marsh <denali@kava.io>
Co-authored-by: John Maheswaran <john@noreply>
Co-authored-by: Kevin Davis <kjydavis3@gmail.com>
This commit is contained in:
jmahess 2020-04-23 19:25:44 -04:00 committed by GitHub
parent 23e23fdaaa
commit 5737f4fa19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 95 additions and 211 deletions

View File

@ -11,23 +11,17 @@ import (
// BeginBlocker compounds the debt in outstanding cdps and liquidates cdps that are below the required collateralization ratio
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
params := k.GetParams(ctx)
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found {
previousBlockTime = ctx.BlockTime()
}
previousDistTime, found := k.GetPreviousSavingsDistribution(ctx)
if !found {
previousDistTime = ctx.BlockTime()
k.SetPreviousSavingsDistribution(ctx, previousDistTime)
}
blockTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
for _, cp := range params.CollateralParams {
for _, dp := range params.DebtParams {
k.HandleNewDebt(ctx, cp.Denom, dp.Denom, blockTimeElapsed)
}
// call our update fees method for the risky cdps
err := k.UpdateFeesForRiskyCdps(ctx, cp.Denom, cp.MarketID)
for _, cp := range params.CollateralParams {
err := k.UpdateFeesForAllCdps(ctx, cp.Denom)
// handle if an error is returned then propagate up
if err != nil {
ctx.EventManager().EmitEvent(
@ -76,6 +70,5 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
}
k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime())
}
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
return
}

View File

@ -152,8 +152,6 @@ func (suite *ModuleTestSuite) TestBeginBlock() {
func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() {
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[0], cs(c("xrp", 10000000000)), cs(c("usdx", 1000000000)))
suite.NoError(err)
suite.keeper.SetPreviousBlockTime(suite.ctx, suite.ctx.BlockTime())
previousBlockTime, _ := suite.keeper.GetPreviousBlockTime(suite.ctx)
suite.Equal(i(1000000000), suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx"))
sk := suite.app.GetSupplyKeeper()
cdpMacc := sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
@ -167,13 +165,10 @@ func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() {
suite.Equal(i(1000000900), (cdpMacc.GetCoins().AmountOf("debt")))
cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 1)
timeElapsed := sdk.NewInt(suite.ctx.BlockTime().Unix() - previousBlockTime.Unix())
fees := suite.keeper.CalculateFees(suite.ctx, cdp.Principal, timeElapsed, "xrp")
suite.Equal(i(928), fees.AmountOf("usdx"))
err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
suite.NoError(err)
_, found := suite.keeper.GetCDP(suite.ctx, "xrp", 1)
suite.False(found)
}
func TestModuleTestSuite(t *testing.T) {

View File

@ -110,7 +110,6 @@ var (
GovDenomKey = types.GovDenomKey
DepositKeyPrefix = types.DepositKeyPrefix
PrincipalKeyPrefix = types.PrincipalKeyPrefix
PreviousBlockTimeKey = types.PreviousBlockTimeKey
PreviousDistributionTimeKey = types.PreviousDistributionTimeKey
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
KeyCollateralParams = types.KeyCollateralParams
@ -128,7 +127,6 @@ var (
DefaultGovDenom = types.DefaultGovDenom
DefaultSurplusThreshold = types.DefaultSurplusThreshold
DefaultDebtThreshold = types.DefaultDebtThreshold
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime
DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency
MaxSortableDec = types.MaxSortableDec

View File

@ -70,10 +70,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, sk SupplyKeeper,
for _, d := range gs.Deposits {
k.SetDeposit(ctx, d)
}
// only set the previous block time if it's different than default
if !gs.PreviousBlockTime.Equal(DefaultPreviousBlockTime) {
k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
}
}
// ExportGenesis export genesis state for cdp module
@ -95,14 +91,10 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
debtDenom := k.GetDebtDenom(ctx)
govDenom := k.GetGovDenom(ctx)
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found {
previousBlockTime = DefaultPreviousBlockTime
}
previousDistributionTime, found := k.GetPreviousSavingsDistribution(ctx)
if !found {
previousDistributionTime = DefaultPreviousDistributionTime
}
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousBlockTime, previousDistributionTime)
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousDistributionTime)
}

View File

@ -70,7 +70,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
@ -153,7 +152,6 @@ func NewCDPGenStateMulti() app.GenesisState {
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
@ -203,19 +201,16 @@ func badGenStates() []badGenState {
g9.DebtDenom = ""
g10 := baseGenState()
g10.PreviousBlockTime = time.Time{}
g10.Params.CollateralParams[0].AuctionSize = i(-10)
g11 := baseGenState()
g11.Params.CollateralParams[0].AuctionSize = i(-10)
g11.Params.CollateralParams[0].LiquidationPenalty = d("5.0")
g12 := baseGenState()
g12.Params.CollateralParams[0].LiquidationPenalty = d("5.0")
g12.GovDenom = ""
g13 := baseGenState()
g13.GovDenom = ""
g14 := baseGenState()
g14.Params.DebtParams[0].SavingsRate = d("4.0")
g13.Params.DebtParams[0].SavingsRate = d("4.0")
return []badGenState{
badGenState{Genesis: g1, Reason: "duplicate collateral denom"},
@ -226,11 +221,10 @@ func badGenStates() []badGenState {
badGenState{Genesis: g6, Reason: "duplicate debt denom"},
badGenState{Genesis: g8, Reason: "debt param not found in global debt limit"},
badGenState{Genesis: g9, Reason: "debt denom not set"},
badGenState{Genesis: g10, Reason: "previous block time not set"},
badGenState{Genesis: g11, Reason: "negative auction size"},
badGenState{Genesis: g12, Reason: "invalid liquidation penalty"},
badGenState{Genesis: g13, Reason: "gov denom not set"},
badGenState{Genesis: g14, Reason: "invalid savings rate"},
badGenState{Genesis: g10, Reason: "negative auction size"},
badGenState{Genesis: g11, Reason: "invalid liquidation penalty"},
badGenState{Genesis: g12, Reason: "gov denom not set"},
badGenState{Genesis: g13, Reason: "invalid savings rate"},
}
}
@ -280,7 +274,6 @@ func baseGenState() cdp.GenesisState {
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
}

View File

@ -40,12 +40,9 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, deposit
k.SetDeposit(ctx, deposit)
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom)
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...))
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio)
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...)
cdp.FeesUpdated = ctx.BlockTime()
cdp.Collateral = cdp.Collateral.Add(collateral...)
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...))
@ -71,15 +68,13 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi
return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.Amount)
}
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom)
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Principal, cdp.AccumulatedFees.Add(fees...))
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Principal, cdp.AccumulatedFees)
if err != nil {
return err
}
liquidationRatio := k.getLiquidationRatio(ctx, collateral[0].Denom)
if collateralizationRatio.LT(liquidationRatio) {
return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "colateral %s, collateral ratio %s, liquidation ration %s", collateral[0].Denom, collateralizationRatio, liquidationRatio)
return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ration %s", collateral[0].Denom, collateralizationRatio, liquidationRatio)
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
@ -96,7 +91,6 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...))
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio)
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...)
cdp.FeesUpdated = ctx.BlockTime()
cdp.Collateral = cdp.Collateral.Sub(collateral)
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...))

View File

@ -25,11 +25,7 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string
return err
}
// fee calculation
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom)
err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal...), cdp.AccumulatedFees.Add(fees...))
err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal...), cdp.AccumulatedFees)
if err != nil {
return err
}
@ -65,7 +61,6 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string
// update cdp state
cdp.Principal = cdp.Principal.Add(principal...)
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...)
cdp.FeesUpdated = ctx.BlockTime()
// increment total principal for the input collateral type
@ -87,16 +82,13 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom)
}
// calculate fees
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom)
err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees...).Add(fees...))
err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees...))
if err != nil {
return err
}
// calculate fee and principal payment
feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees...).Add(fees...), cdp.AccumulatedFees.Add(fees...), payment)
feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), cdp.AccumulatedFees, payment)
// send the payment from the sender to the cpd module
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, feePayment.Add(principalPayment...))
@ -142,7 +134,7 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri
if !principalPayment.IsZero() {
cdp.Principal = cdp.Principal.Sub(principalPayment)
}
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...).Sub(feePayment)
cdp.AccumulatedFees = cdp.AccumulatedFees.Sub(feePayment)
cdp.FeesUpdated = ctx.BlockTime()
// decrement the total principal for the input collateral type

View File

@ -159,11 +159,12 @@ func (suite *DrawTestSuite) TestAddRepayPrincipalFees() {
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[2], cs(c("xrp", 1000000000000)), cs(c("usdx", 100000000000)))
suite.NoError(err)
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Minute * 10))
err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp")
suite.NoError(err)
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 10000000)))
suite.NoError(err)
t, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
suite.Equal(cs(c("usdx", 92827)), t.AccumulatedFees)
_ = suite.keeper.MintDebtCoins(suite.ctx, types.ModuleName, "debt", cs(c("usdx", 92827)))
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100)))
suite.NoError(err)
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
@ -176,7 +177,9 @@ func (suite *DrawTestSuite) TestAddRepayPrincipalFees() {
err = suite.keeper.AddCdp(suite.ctx, suite.addrs[2], cs(c("xrp", 1000000000000)), cs(c("usdx", 100000000)))
suite.NoError(err)
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000))
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000)) // move forward one year in time
err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp")
suite.NoError(err)
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100000000)))
suite.NoError(err)
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(3))

View File

@ -1,8 +1,6 @@
package keeper
import (
"time"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/cdp/types"
@ -27,38 +25,53 @@ func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coins, periods sdk.
return newFees
}
// UpdateFeesForRiskyCdps calculates fees for risky CDPs
// The overall logic is first select the CDPs with 10% of the liquidation ratio
// Then we call calculate fees on each of those CDPs
// Next we store the result of the fees in the cdp.AccumulatedFees field
// Finally we set the cdp.FeesUpdated time to the current block time (ctx.BlockTime()) since that
// is when we made the update
func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, marketID string) error {
// UpdateFeesForAllCdps updates the fees for each of the CDPs
func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) error {
price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID)
if err != nil {
return err
}
k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool {
liquidationRatio := k.getLiquidationRatio(ctx, collateralDenom)
priceDivLiqRatio := price.Price.Quo(liquidationRatio)
if priceDivLiqRatio.IsZero() {
priceDivLiqRatio = sdk.SmallestDec()
}
// NOTE - we have a fixed cutoff at 110% - this may or may not be changed in the future
normalizedRatio := sdk.OneDec().Quo(priceDivLiqRatio).Mul(sdk.MustNewDecFromStr("1.1"))
// now iterate over all the cdps based on collateral ratio
k.IterateCdpsByCollateralRatio(ctx, collateralDenom, normalizedRatio, func(cdp types.CDP) bool {
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...))
// get the number of periods
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
// now calculate and store additional fees
additionalFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom)
newFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom)
// now add the additional fees to the accumulated fees for the cdp
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(additionalFees...)
// exit without updating fees if amount has rounded down to zero
// cdp will get updated next block when newFees, newFeesSavings, newFeesSurplus >0
if newFees.IsZero() {
return false
}
// note - only works if principal length is one
for _, dc := range cdp.Principal {
dp, found := k.GetDebtParam(ctx, dc.Denom)
if !found {
return false
}
savingsRate := dp.SavingsRate
newFeesSavings := sdk.NewDecFromInt(newFees.AmountOf(dp.Denom)).Mul(savingsRate).RoundInt()
newFeesSurplus := newFees.AmountOf(dp.Denom).Sub(newFeesSavings)
// similar to checking for rounding to zero of all fees, but in this case we
// need to handle cases where we expect surplus or savings fees to be zero, namely
// if newFeesSavings = 0, check if savings rate is not zero
// if newFeesSurplus = 0, check if savings rate is not one
if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) {
return false
}
// mint debt coins to the cdp account
k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom)
feeCoins := sdk.NewCoins(sdk.NewCoin(dp.Denom, previousDebt))
k.SetTotalPrincipal(ctx, collateralDenom, dp.Denom, feeCoins.Add(newFees...).AmountOf(dp.Denom))
// mint surplus coins divided between the liquidator and savings module accounts.
k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus)))
k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSavings)))
}
// now add the new fees fees to the accumulated fees for the cdp
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(newFees...)
// and set the fees updated time to the current block time since we just updated it
cdp.FeesUpdated = ctx.BlockTime()
@ -109,20 +122,3 @@ func (k Keeper) SetTotalPrincipal(ctx sdk.Context, collateralDenom string, princ
store := prefix.NewStore(ctx.KVStore(k.key), types.PrincipalKeyPrefix)
store.Set([]byte(collateralDenom+principalDenom), k.cdc.MustMarshalBinaryLengthPrefixed(total))
}
// GetPreviousBlockTime get the blocktime for the previous block
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
b := store.Get([]byte{})
if b == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &blockTime)
return blockTime, true
}
// SetPreviousBlockTime set the time of the previous block
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(blockTime))
}

View File

@ -68,10 +68,6 @@ func (suite *FeeTestSuite) TestCalculateFeesPrecisionLoss() {
absError := (sdk.OneDec().Sub(sdk.NewDecFromInt(bulkFees[0].Amount).Quo(sdk.NewDecFromInt(individualFees[0].Amount)))).Abs()
suite.T().Log(bulkFees)
suite.T().Log(individualFees)
suite.T().Log(absError)
suite.True(d("0.00001").GTE(absError))
}
@ -95,28 +91,26 @@ func (suite *FeeTestSuite) createCdps() {
// now create two cdps with the addresses we just created
// use the created account to create a cdp that SHOULD have fees updated
// to get a ratio between 100 - 110% of liquidation ratio we can use 200xrp ($50) and 24 usdx (208% collateralization with liquidation ratio of 200%)
// create CDP for the first address
err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("usdx", 24000000)))
suite.NoError(err) // check that no error was thrown
// use the other account to create a cdp that SHOULD NOT have fees updated - 500% collateralization
// create CDP for the second address
// use the other account to create a cdp that SHOULD NOT have fees updated
err = suite.keeper.AddCdp(suite.ctx, addrs[1], cs(c("xrp", 200000000)), cs(c("usdx", 10000000)))
suite.NoError(err) // check that no error was thrown
}
// UpdateFeesForRiskyCdpsTest tests the functionality for updating the fees for risky CDPs
func (suite *FeeTestSuite) TestUpdateFeesForRiskyCdps() {
// TestUpdateFees tests the functionality for updating the fees for CDPs
func (suite *FeeTestSuite) TestUpdateFees() {
// this helper function creates two CDPs with id 1 and 2 respectively, each with zero fees
suite.createCdps()
// move the context forward in time so that cdps will have fees accumulate if CalculateFees is called
// note - time must be moved forward by a sufficient amount in order for additional
// fees to accumulate, in this example 60 seconds
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 60))
err := suite.keeper.UpdateFeesForRiskyCdps(suite.ctx, "xrp", "xrp:usd")
// fees to accumulate, in this example 600 seconds
oldtime := suite.ctx.BlockTime()
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 600))
err := suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp")
suite.NoError(err) // check that we don't have any error
// cdp we expect fees to accumulate for
@ -124,29 +118,15 @@ func (suite *FeeTestSuite) TestUpdateFeesForRiskyCdps() {
// check fees are not zero
// check that the fees have been updated
suite.False(cdp1.AccumulatedFees.Empty())
// now check that we have the correct amount of fees overall (2 USDX for this scenario)
suite.Equal(sdk.NewInt(2), cdp1.AccumulatedFees.AmountOf("usdx"))
// cdp we expect fees to not accumulate for
// now check that we have the correct amount of fees overall (22 USDX for this scenario)
suite.Equal(sdk.NewInt(22), cdp1.AccumulatedFees.AmountOf("usdx"))
suite.Equal(suite.ctx.BlockTime(), cdp1.FeesUpdated)
// cdp we expect fees to not accumulate for because of rounding to zero
cdp2, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 2)
// check fees are zero
suite.True(cdp2.AccumulatedFees.Empty())
}
func (suite *FeeTestSuite) TestGetSetPreviousBlockTime() {
now := tmtime.Now()
_, f := suite.keeper.GetPreviousBlockTime(suite.ctx)
suite.False(f)
suite.NotPanics(func() { suite.keeper.SetPreviousBlockTime(suite.ctx, now) })
bpt, f := suite.keeper.GetPreviousBlockTime(suite.ctx)
suite.True(f)
suite.Equal(now, bpt)
suite.Equal(oldtime, cdp2.FeesUpdated)
}
func TestFeeTestSuite(t *testing.T) {

View File

@ -70,7 +70,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
@ -153,7 +152,6 @@ func NewCDPGenStateMulti() app.GenesisState {
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
@ -211,7 +209,6 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}

View File

@ -18,11 +18,6 @@ import (
func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
// Calculate the previous collateral ratio
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...))
// Update fees
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom)
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...)
cdp.FeesUpdated = ctx.BlockTime()
// Move debt coins from cdp to liquidator account
deposits := k.GetDeposits(ctx, cdp.ID)
@ -80,29 +75,6 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
return nil
}
// HandleNewDebt compounds the accumulated fees for the input collateral and principal coins.
// the following operations are performed:
// 1. The fees accumulated since the last block are calculated
// 2. The fees are minted, split between the liquidator module account (surplus) and the savings rate module account (savings rate) according to the savings rate parameter.
// 2. An equal amount of debt coins are minted in the cdp module account
// 3. updates the total amount of principal for the input collateral type in the store,
func (k Keeper) HandleNewDebt(ctx sdk.Context, collateralDenom string, principalDenom string, periods sdk.Int) {
dp, _ := k.GetDebtParam(ctx, principalDenom)
savingsRate := dp.SavingsRate
previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, principalDenom)
feeCoins := sdk.NewCoins(sdk.NewCoin(principalDenom, previousDebt))
newFees := k.CalculateFees(ctx, feeCoins, periods, collateralDenom)
if newFees.IsZero() {
return
}
newFeesSavings := sdk.NewDecFromInt(newFees.AmountOf(principalDenom)).Mul(savingsRate).RoundInt()
newFeesSurplus := newFees.AmountOf(principalDenom).Sub(newFeesSavings)
k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(principalDenom, newFeesSurplus)))
k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(principalDenom, newFeesSavings)))
k.SetTotalPrincipal(ctx, collateralDenom, principalDenom, feeCoins.Add(newFees...).AmountOf(principalDenom))
}
// LiquidateCdps seizes collateral from all CDPs below the input liquidation ratio
func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, liquidationRatio sdk.Dec) error {
price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID)

View File

@ -189,14 +189,6 @@ func (suite *SeizeTestSuite) TestLiquidateCdps() {
suite.Equal(len(suite.liquidations.xrp), xrpLiquidations)
}
func (suite *SeizeTestSuite) TestHandleNewDebt() {
suite.createCdps()
tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
suite.keeper.HandleNewDebt(suite.ctx, "xrp", "usdx", i(31536000))
tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
suite.Equal(sdk.NewDec(tpb.Int64()).Mul(d("1.05")).TruncateInt().Int64(), tpa.Int64())
}
func (suite *SeizeTestSuite) TestApplyLiquidationPenalty() {
penalty := suite.keeper.ApplyLiquidationPenalty(suite.ctx, "xrp", i(1000))
suite.Equal(i(50), penalty)

View File

@ -54,8 +54,7 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &totalB)
return fmt.Sprintf("%s\n%s", totalA, totalB)
case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey),
bytes.Equal(kvA.Key[:1], types.PreviousDistributionTimeKey):
case bytes.Equal(kvA.Key[:1], types.PreviousDistributionTimeKey):
var timeA, timeB time.Time
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA)
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB)

View File

@ -42,7 +42,7 @@ func TestDecodeDistributionStore(t *testing.T) {
kv.Pair{Key: []byte(types.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)},
kv.Pair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)},
kv.Pair{Key: []byte(types.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)},
kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)},
kv.Pair{Key: []byte(types.PreviousDistributionTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)},
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
}

View File

@ -121,7 +121,6 @@ func randomCdpGenState(selection int) types.GenesisState {
DebtDenom: types.DefaultDebtDenom,
GovDenom: types.DefaultGovDenom,
CDPs: types.CDPs{},
PreviousBlockTime: types.DefaultPreviousBlockTime,
PreviousDistributionTime: types.DefaultPreviousDistributionTime,
}
case 1:
@ -158,7 +157,6 @@ func randomCdpGenState(selection int) types.GenesisState {
DebtDenom: types.DefaultDebtDenom,
GovDenom: types.DefaultGovDenom,
CDPs: types.CDPs{},
PreviousBlockTime: types.DefaultPreviousBlockTime,
PreviousDistributionTime: types.DefaultPreviousDistributionTime,
}
default:

View File

@ -14,12 +14,11 @@ type GenesisState struct {
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml"previous_distribution_time"`
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml:"previous_distribution_time"`
}
// NewGenesisState returns a new genesis state
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64, debtDenom, govDenom string, previousBlockTime time.Time, previousDistTime time.Time) GenesisState {
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64, debtDenom, govDenom string, previousDistTime time.Time) GenesisState {
return GenesisState{
Params: params,
CDPs: cdps,
@ -27,23 +26,21 @@ func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID
StartingCdpID: startingCdpID,
DebtDenom: debtDenom,
GovDenom: govDenom,
PreviousBlockTime: previousBlockTime,
PreviousDistributionTime: previousDistTime,
}
}
// DefaultGenesisState returns a default genesis state
func DefaultGenesisState() GenesisState {
return GenesisState{
Params: DefaultParams(),
CDPs: CDPs{},
Deposits: Deposits{},
StartingCdpID: DefaultCdpStartingID,
DebtDenom: DefaultDebtDenom,
GovDenom: DefaultGovDenom,
PreviousBlockTime: DefaultPreviousBlockTime,
PreviousDistributionTime: DefaultPreviousDistributionTime,
}
return NewGenesisState(
DefaultParams(),
CDPs{},
Deposits{},
DefaultCdpStartingID,
DefaultDebtDenom,
DefaultGovDenom,
DefaultPreviousDistributionTime,
)
}
// Validate performs basic validation of genesis data returning an
@ -54,10 +51,6 @@ func (gs GenesisState) Validate() error {
return err
}
if gs.PreviousBlockTime.Equal(time.Time{}) {
return fmt.Errorf("previous block time not set")
}
if gs.PreviousDistributionTime.Equal(time.Time{}) {
return fmt.Errorf("previous distribution time not set")
}

View File

@ -45,8 +45,7 @@ var sep = []byte(":")
// - 0x05<depositState>:<cdpID>:<depositorAddr_bytes>: Deposit
// - 0x06<denom>:totalPrincipal
// - 0x07<denom>:feeRate
// - 0x08:previousBlockTime
// - 0x09:previousDistributionTime
// - 0x08:previousDistributionTime
// KVStore key prefixes
var (
@ -58,8 +57,7 @@ var (
GovDenomKey = []byte{0x05}
DepositKeyPrefix = []byte{0x06}
PrincipalKeyPrefix = []byte{0x07}
PreviousBlockTimeKey = []byte{0x08}
PreviousDistributionTimeKey = []byte{0x09}
PreviousDistributionTimeKey = []byte{0x08}
)
var lenPositiveDec = len(SortableDecBytes(sdk.OneDec()))

View File

@ -29,7 +29,6 @@ var (
DefaultGovDenom = "ukava"
DefaultSurplusThreshold = sdk.NewInt(1000000000)
DefaultDebtThreshold = sdk.NewInt(1000000000)
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
DefaultSavingsDistributionFrequency = time.Hour * 24 * 2
minCollateralPrefix = 0