cleanup begin/end block (#553)

* cleanup begin/end block

* update cdp BeginBlocker

* update dist mint

* fix tests

* typo

* add comment on CloseExpiredAuctions

* fix tests
This commit is contained in:
Federico Kunze 2020-06-17 11:09:44 +02:00 committed by GitHub
parent f0e73e4aa8
commit bfb9f7e20a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 166 additions and 141 deletions

View File

@ -1,13 +1,17 @@
package auction package auction
import ( import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/auction/types"
) )
// BeginBlocker runs at the start of every block. // BeginBlocker closes all expired auctions at the end of each block. It panics if
// there's an error other than ErrAuctionNotFound.
func BeginBlocker(ctx sdk.Context, k Keeper) { func BeginBlocker(ctx sdk.Context, k Keeper) {
err := k.CloseExpiredAuctions(ctx) err := k.CloseExpiredAuctions(ctx)
if err != nil { if err != nil && !errors.Is(err, types.ErrAuctionNotFound) {
panic(err) panic(err)
} }
} }

View File

@ -1,6 +1,7 @@
package keeper package keeper
import ( import (
"errors"
"fmt" "fmt"
"time" "time"
@ -449,7 +450,6 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, auction types.DebtAuction, bidder
// CloseAuction closes an auction and distributes funds to the highest bidder. // CloseAuction closes an auction and distributes funds to the highest bidder.
func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) error { func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) error {
auction, found := k.GetAuction(ctx, auctionID) auction, found := k.GetAuction(ctx, auctionID)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", auctionID) return sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", auctionID)
@ -460,21 +460,20 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) error {
} }
// payout to the last bidder // payout to the last bidder
var err error
switch auc := auction.(type) { switch auc := auction.(type) {
case types.SurplusAuction: case types.SurplusAuction:
if err := k.PayoutSurplusAuction(ctx, auc); err != nil { err = k.PayoutSurplusAuction(ctx, auc)
return err
}
case types.DebtAuction: case types.DebtAuction:
if err := k.PayoutDebtAuction(ctx, auc); err != nil { err = k.PayoutDebtAuction(ctx, auc)
return err
}
case types.CollateralAuction: case types.CollateralAuction:
if err := k.PayoutCollateralAuction(ctx, auc); err != nil { err = k.PayoutCollateralAuction(ctx, auc)
return err
}
default: default:
return sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType()) err = sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType())
}
if err != nil {
return err
} }
k.DeleteAuction(ctx, auctionID) k.DeleteAuction(ctx, auctionID)
@ -502,23 +501,17 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, auction types.DebtAuction) er
return err return err
} }
// if there is remaining debt, return it to the calling module to manage // if there is remaining debt, return it to the calling module to manage
if auction.CorrespondingDebt.IsPositive() { if !auction.CorrespondingDebt.IsPositive() {
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, auction.Initiator, sdk.NewCoins(auction.CorrespondingDebt))
if err != nil {
return err
}
}
return nil return nil
}
return k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, auction.Initiator, sdk.NewCoins(auction.CorrespondingDebt))
} }
// PayoutSurplusAuction pays out the proceeds for a surplus auction. // PayoutSurplusAuction pays out the proceeds for a surplus auction.
func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, auction types.SurplusAuction) error { func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, auction types.SurplusAuction) error {
// Send the tokens from the auction module account where they are being managed to the bidder who won the auction // Send the tokens from the auction module account where they are being managed to the bidder who won the auction
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, auction.Bidder, sdk.NewCoins(auction.Lot)) return k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, auction.Bidder, sdk.NewCoins(auction.Lot))
if err != nil {
return err
}
return nil
} }
// PayoutCollateralAuction pays out the proceeds for a collateral auction. // PayoutCollateralAuction pays out the proceeds for a collateral auction.
@ -530,29 +523,30 @@ func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, auction types.Collatera
} }
// if there is remaining debt after the auction, send it back to the initiating module for management // if there is remaining debt after the auction, send it back to the initiating module for management
if auction.CorrespondingDebt.IsPositive() { if !auction.CorrespondingDebt.IsPositive() {
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, auction.Initiator, sdk.NewCoins(auction.CorrespondingDebt))
if err != nil {
return err
}
}
return nil return nil
}
return k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, auction.Initiator, sdk.NewCoins(auction.CorrespondingDebt))
} }
// CloseExpiredAuctions finds all auctions that are past (or at) their ending times and closes them, paying out to the highest bidder. // CloseExpiredAuctions iterates over all the auctions stored by until the current
// block timestamp and that are past (or at) their ending times and closes them,
// paying out to the highest bidder.
func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) error { func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) error {
var expiredAuctions []uint64 var err error
k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) (stop bool) {
expiredAuctions = append(expiredAuctions, id) err = k.CloseAuction(ctx, id)
if err != nil && !errors.Is(err, types.ErrAuctionNotFound) {
// stop iteration
return true
}
// reset error in case the last element had an ErrAuctionNotFound
err = nil
return false return false
}) })
// Note: iteration and auction closing are in separate loops as db should not be modified during iteration // TODO is this correct? gov modifies during iteration
for _, id := range expiredAuctions {
if err := k.CloseAuction(ctx, id); err != nil {
return err return err
}
}
return nil
} }
// earliestTime returns the earliest of two times. // earliestTime returns the earliest of two times.

View File

@ -1,9 +1,13 @@
package cdp package cdp
import ( import (
"errors"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
) )
// BeginBlocker compounds the debt in outstanding cdps and liquidates cdps that are below the required collateralization ratio // BeginBlocker compounds the debt in outstanding cdps and liquidates cdps that are below the required collateralization ratio
@ -17,7 +21,6 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
} }
for _, cp := range params.CollateralParams { for _, cp := range params.CollateralParams {
ok := k.UpdatePricefeedStatus(ctx, cp.SpotMarketID) ok := k.UpdatePricefeedStatus(ctx, cp.SpotMarketID)
if !ok { if !ok {
continue continue
@ -29,26 +32,30 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
} }
err := k.UpdateFeesForAllCdps(ctx, cp.Denom) err := k.UpdateFeesForAllCdps(ctx, cp.Denom)
if err != nil {
panic(err)
}
if err != nil {
panic(err)
}
err = k.LiquidateCdps(ctx, cp.LiquidationMarketID, cp.Denom, cp.LiquidationRatio) err = k.LiquidateCdps(ctx, cp.LiquidationMarketID, cp.Denom, cp.LiquidationRatio)
if err != nil { if err != nil && !errors.Is(err, pricefeedtypes.ErrNoValidPrice) {
panic(err) panic(err)
} }
} }
err := k.RunSurplusAndDebtAuctions(ctx) err := k.RunSurplusAndDebtAuctions(ctx)
if err != nil { if err != nil {
panic(err) panic(err)
} }
distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix()) distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix())
if !distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) { if !distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) {
return return
} }
err = k.DistributeSavingsRate(ctx, params.DebtParam.Denom) err = k.DistributeSavingsRate(ctx, params.DebtParam.Denom)
if err != nil { if err != nil {
panic(err) panic(err)
} }
k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime()) k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime())
} }

View File

@ -149,11 +149,13 @@ func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) error {
} }
remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc) remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
params := k.GetParams(ctx) params := k.GetParams(ctx)
if remainingDebt.GTE(params.DebtAuctionThreshold) { if remainingDebt.GTE(params.DebtAuctionThreshold) {
debtLot := sdk.NewCoin(k.GetDebtDenom(ctx), params.DebtAuctionLot) debtLot := sdk.NewCoin(k.GetDebtDenom(ctx), params.DebtAuctionLot)
bidCoin := sdk.NewCoin(params.DebtParam.Denom, debtLot.Amount) bidCoin := sdk.NewCoin(params.DebtParam.Denom, debtLot.Amount)
_, err := k.auctionKeeper.StartDebtAuction( initialLot := sdk.NewCoin(k.GetGovDenom(ctx), debtLot.Amount.Mul(sdk.NewInt(dump)))
ctx, types.LiquidatorMacc, bidCoin, sdk.NewCoin(k.GetGovDenom(ctx), debtLot.Amount.Mul(sdk.NewInt(dump))), debtLot)
_, err := k.auctionKeeper.StartDebtAuction(ctx, types.LiquidatorMacc, bidCoin, initialLot, debtLot)
if err != nil { if err != nil {
return err return err
} }
@ -163,6 +165,7 @@ func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) error {
if !surplus.GTE(params.SurplusAuctionThreshold) { if !surplus.GTE(params.SurplusAuctionThreshold) {
return nil return nil
} }
surplusLot := sdk.NewCoin(params.DebtParam.Denom, sdk.MinInt(params.SurplusAuctionLot, surplus)) surplusLot := sdk.NewCoin(params.DebtParam.Denom, sdk.MinInt(params.SurplusAuctionLot, surplus))
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx)) _, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx))
return err return err

View File

@ -26,8 +26,8 @@ func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coin, periods sdk.I
func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) error { func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) error {
var iterationErr error var iterationErr error
k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool { k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool {
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
// periods = bblock timestamp - fees updated
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
newFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom) newFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom)
@ -55,12 +55,14 @@ func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) er
if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) { if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) {
return false return false
} }
// mint debt coins to the cdp account // mint debt coins to the cdp account
err := k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees) err := k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
if err != nil { if err != nil {
iterationErr = err iterationErr = err
return true return true
} }
previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom) previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom)
newDebt := previousDebt.Add(newFees.Amount) newDebt := previousDebt.Add(newFees.Amount)
k.SetTotalPrincipal(ctx, collateralDenom, dp.Denom, newDebt) k.SetTotalPrincipal(ctx, collateralDenom, dp.Denom, newDebt)
@ -100,17 +102,14 @@ func (k Keeper) IncrementTotalPrincipal(ctx sdk.Context, collateralDenom string,
total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom) total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom)
total = total.Add(principal.Amount) total = total.Add(principal.Amount)
k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total) k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total)
} }
// DecrementTotalPrincipal decrements the total amount of debt that has been drawn for a particular collateral type // DecrementTotalPrincipal decrements the total amount of debt that has been drawn for a particular collateral type
func (k Keeper) DecrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coin) { func (k Keeper) DecrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coin) {
total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom) total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom)
total = total.Sub(principal.Amount) // NOTE: negative total principal can happen in tests due to rounding errors
if total.IsNegative() { // in fee calculation
// can happen in tests due to rounding errors in fee calculation total = sdk.MaxInt(total.Sub(principal.Amount), sdk.ZeroInt())
total = sdk.ZeroInt()
}
k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total) k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total)
} }

View File

@ -18,6 +18,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
if !found { if !found {
return sdkerrors.Wrap(types.ErrDebtNotSupported, debtDenom) return sdkerrors.Wrap(types.ErrDebtNotSupported, debtDenom)
} }
savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc) savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc)
surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom) surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom)
if surplusToDistribute.IsZero() { if surplusToDistribute.IsZero() {
@ -26,21 +27,26 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
modAccountCoins := k.getModuleAccountCoins(ctx, dp.Denom) modAccountCoins := k.getModuleAccountCoins(ctx, dp.Denom)
totalSupplyLessModAccounts := k.supplyKeeper.GetSupply(ctx).GetTotal().Sub(modAccountCoins) totalSupplyLessModAccounts := k.supplyKeeper.GetSupply(ctx).GetTotal().Sub(modAccountCoins)
// values to use in interest calculation // values to use in interest calculation
totalSurplus := sdk.NewDecFromInt(surplusToDistribute) totalSurplus := sdk.NewDecFromInt(surplusToDistribute)
totalSupply := sdk.NewDecFromInt(totalSupplyLessModAccounts.AmountOf(debtDenom)) totalSupply := sdk.NewDecFromInt(totalSupplyLessModAccounts.AmountOf(debtDenom))
var iterationErr error 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) { k.accountKeeper.IterateAccounts(ctx, func(acc authexported.Account) (stop bool) {
_, ok := acc.(supplyexported.ModuleAccountI) _, ok := acc.(supplyexported.ModuleAccountI)
if ok { if ok {
// don't distribute savings rate to module accounts // don't distribute savings rate to module accounts
return false return false
} }
debtAmount := acc.GetCoins().AmountOf(debtDenom) debtAmount := acc.GetCoins().AmountOf(debtDenom)
if !debtAmount.IsPositive() { if !debtAmount.IsPositive() {
return false return false
} }
// (balance * rewardToDisribute) / totalSupply // (balance * rewardToDisribute) / totalSupply
// interest is the ratable fraction of savings rate owed to that account, rounded using bankers rounding // 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() interest := (sdk.NewDecFromInt(debtAmount).Mul(totalSurplus)).Quo(totalSupply).RoundInt()
@ -51,6 +57,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
if !interest.IsPositive() { if !interest.IsPositive() {
return false return false
} }
interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest)) interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest))
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins) err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins)
if err != nil { if err != nil {
@ -60,6 +67,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error {
surplusToDistribute = surplusToDistribute.Sub(interest) surplusToDistribute = surplusToDistribute.Sub(interest)
return false return false
}) })
return iterationErr return iterationErr
} }

View File

@ -24,9 +24,7 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
deposits := k.GetDeposits(ctx, cdp.ID) deposits := k.GetDeposits(ctx, cdp.ID)
debt := cdp.Principal.Amount.Add(cdp.AccumulatedFees.Amount) debt := cdp.Principal.Amount.Add(cdp.AccumulatedFees.Amount)
modAccountDebt := k.getModAccountDebt(ctx, types.ModuleName) modAccountDebt := k.getModAccountDebt(ctx, types.ModuleName)
if modAccountDebt.LT(debt) { debt = sdk.MinInt(debt, modAccountDebt)
debt = modAccountDebt
}
debtCoin := sdk.NewCoin(k.GetDebtDenom(ctx), debt) debtCoin := sdk.NewCoin(k.GetDebtDenom(ctx), debt)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(debtCoin)) err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(debtCoin))
if err != nil { if err != nil {
@ -35,6 +33,12 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
// liquidate deposits and send collateral from cdp to liquidator // liquidate deposits and send collateral from cdp to liquidator
for _, dep := range deposits { for _, dep := range deposits {
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(dep.Amount))
if err != nil {
return err
}
k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor)
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeCdpLiquidation, types.EventTypeCdpLiquidation,
@ -43,12 +47,8 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
sdk.NewAttribute(types.AttributeKeyDeposit, dep.String()), sdk.NewAttribute(types.AttributeKeyDeposit, dep.String()),
), ),
) )
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(dep.Amount))
if err != nil {
return err
}
k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor)
} }
err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal.Denom) err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal.Denom)
if err != nil { if err != nil {
return err return err

View File

@ -8,9 +8,7 @@ import (
// BeginBlocker runs at the start of every block. // BeginBlocker runs at the start of every block.
func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k Keeper) { func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k Keeper) {
// enact proposals ignoring their expiry time - they could have received enough votes last block before expiring this block // enact proposals ignoring their expiry time - they could have received enough votes last block before expiring this block
k.EnactPassedProposals(ctx) k.EnactPassedProposals(ctx)
k.CloseExpiredProposals(ctx) k.CloseExpiredProposals(ctx)
} }

View File

@ -207,9 +207,7 @@ func (k Keeper) GetProposalsByCommittee(ctx sdk.Context, committeeID uint64) []t
// DeleteProposalAndVotes removes a proposal and its associated votes. // DeleteProposalAndVotes removes a proposal and its associated votes.
func (k Keeper) DeleteProposalAndVotes(ctx sdk.Context, proposalID uint64) { func (k Keeper) DeleteProposalAndVotes(ctx sdk.Context, proposalID uint64) {
votes := k.GetVotesByProposal(ctx, proposalID) votes := k.GetVotesByProposal(ctx, proposalID)
k.DeleteProposal(ctx, proposalID) k.DeleteProposal(ctx, proposalID)
for _, v := range votes { for _, v := range votes {
k.DeleteVote(ctx, v.ProposalID, v.Voter) k.DeleteVote(ctx, v.ProposalID, v.Voter)

View File

@ -138,7 +138,9 @@ func (k Keeper) EnactPassedProposals(ctx sdk.Context) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
if !passes { if !passes {
// continue to next proposal
return false return false
} }
@ -164,7 +166,6 @@ func (k Keeper) EnactPassedProposals(ctx sdk.Context) {
// CloseExpiredProposals removes proposals (and associated votes) that have past their deadline. // CloseExpiredProposals removes proposals (and associated votes) that have past their deadline.
func (k Keeper) CloseExpiredProposals(ctx sdk.Context) { func (k Keeper) CloseExpiredProposals(ctx sdk.Context) {
k.IterateProposals(ctx, func(proposal types.Proposal) bool { k.IterateProposals(ctx, func(proposal types.Proposal) bool {
if !proposal.HasExpiredBy(ctx.BlockTime()) { if !proposal.HasExpiredBy(ctx.BlockTime()) {
return false return false

View File

@ -48,7 +48,8 @@ func (k Keeper) CreateAndDeleteRewardPeriods(ctx sdk.Context) {
} }
} }
// ApplyRewardsToCdps iterates over the reward periods and creates a claim for each cdp owner that created usdx with the collateral specified in the reward period // ApplyRewardsToCdps iterates over the reward periods and creates a claim for each
// cdp owner that created usdx with the collateral specified in the reward period.
func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) { func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
previousBlockTime, found := k.GetPreviousBlockTime(ctx) previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found { if !found {
@ -56,6 +57,7 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
k.SetPreviousBlockTime(ctx, previousBlockTime) k.SetPreviousBlockTime(ctx, previousBlockTime)
return return
} }
k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool { k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool {
expired := false expired := false
// the total amount of usdx created with the collateral type being incentivized // the total amount of usdx created with the collateral type being incentivized
@ -66,6 +68,7 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
timeElapsed = sdk.NewInt(rp.End.Unix() - previousBlockTime.Unix()) timeElapsed = sdk.NewInt(rp.End.Unix() - previousBlockTime.Unix())
expired = true expired = true
} }
// the amount of rewards to pay (rewardAmount * timeElapsed) // the amount of rewards to pay (rewardAmount * timeElapsed)
rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed) rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed)
id := k.GetNextClaimPeriodID(ctx, rp.Denom) id := k.GetNextClaimPeriodID(ctx, rp.Denom)
@ -85,6 +88,7 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
k.HandleRewardPeriodExpiry(ctx, rp) k.HandleRewardPeriodExpiry(ctx, rp)
return false return false
}) })
k.SetPreviousBlockTime(ctx, ctx.BlockTime()) k.SetPreviousBlockTime(ctx, ctx.BlockTime())
} }

View File

@ -19,43 +19,45 @@ func (k Keeper) MintPeriodInflation(ctx sdk.Context) error {
) )
return nil return nil
} }
previousBlockTime, found := k.GetPreviousBlockTime(ctx) previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found { if !found {
previousBlockTime = ctx.BlockTime() previousBlockTime = ctx.BlockTime()
k.SetPreviousBlockTime(ctx, previousBlockTime) k.SetPreviousBlockTime(ctx, previousBlockTime)
return nil return nil
} }
var err error
for _, period := range params.Periods { for _, period := range params.Periods {
switch {
// Case 1 - period is fully expired // Case 1 - period is fully expired
if period.End.Before(previousBlockTime) { case period.End.Before(previousBlockTime):
continue continue
}
// Case 2 - period has ended since the previous block time // Case 2 - period has ended since the previous block time
if period.End.After(previousBlockTime) && period.End.Before(ctx.BlockTime()) { case period.End.After(previousBlockTime) && period.End.Before(ctx.BlockTime()):
// calculate time elapsed relative to the periods end time // calculate time elapsed relative to the periods end time
timeElapsed := sdk.NewInt(period.End.Unix() - previousBlockTime.Unix()) timeElapsed := sdk.NewInt(period.End.Unix() - previousBlockTime.Unix())
err := k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom) err = k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom)
if err != nil {
return err
}
// update the value of previousBlockTime so that the next period starts from the end of the last // update the value of previousBlockTime so that the next period starts from the end of the last
// period and not the original value of previousBlockTime // period and not the original value of previousBlockTime
previousBlockTime = period.End previousBlockTime = period.End
}
// Case 3 - period is ongoing // Case 3 - period is ongoing
if (period.Start.Before(previousBlockTime) || period.Start.Equal(previousBlockTime)) && period.End.After(ctx.BlockTime()) { case (period.Start.Before(previousBlockTime) || period.Start.Equal(previousBlockTime)) && period.End.After(ctx.BlockTime()):
// calculate time elapsed relative to the current block time // calculate time elapsed relative to the current block time
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix()) timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
err := k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom) err = k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom)
// Case 4 - period hasn't started
case period.Start.After(ctx.BlockTime()) || period.Start.Equal(ctx.BlockTime()):
continue
}
if err != nil { if err != nil {
return err return err
} }
} }
// Case 4 - period hasn't started
if period.Start.After(ctx.BlockTime()) || period.Start.Equal(ctx.BlockTime()) {
continue
}
}
k.SetPreviousBlockTime(ctx, ctx.BlockTime()) k.SetPreviousBlockTime(ctx, ctx.BlockTime())
return nil return nil
} }
@ -78,11 +80,13 @@ func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, ti
if err != nil { if err != nil {
return err return err
} }
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeKavaDist, types.EventTypeKavaDist,
sdk.NewAttribute(types.AttributeKeyInflation, sdk.NewCoin(denom, amountToMint).String()), sdk.NewAttribute(types.AttributeKeyInflation, sdk.NewCoin(denom, amountToMint).String()),
), ),
) )
return nil return nil
} }

View File

@ -1,28 +1,23 @@
package pricefeed package pricefeed
import ( import (
"fmt" "errors"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/pricefeed/types"
) )
// EndBlocker updates the current pricefeed // EndBlocker updates the current pricefeed
func EndBlocker(ctx sdk.Context, k Keeper) { func EndBlocker(ctx sdk.Context, k Keeper) {
// Update the current price of each asset. // Update the current price of each asset.
for _, market := range k.GetMarkets(ctx) { for _, market := range k.GetMarkets(ctx) {
if market.Active { if !market.Active {
err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil {
// In the event of failure, emit an event.
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeNoValidPrices,
sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
),
)
continue continue
} }
err := k.SetCurrentPrices(ctx, market.MarketID)
if err != nil && !errors.Is(err, types.ErrNoValidPrice) {
panic(err)
} }
} }
return
} }

View File

@ -1,9 +1,12 @@
package keeper package keeper
import ( import (
"fmt"
"sort" "sort"
"time" "time"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -37,6 +40,11 @@ func NewKeeper(
} }
} }
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// SetPrice updates the posted price for a specific oracle // SetPrice updates the posted price for a specific oracle
func (k Keeper) SetPrice( func (k Keeper) SetPrice(
ctx sdk.Context, ctx sdk.Context,
@ -111,20 +119,21 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
notExpiredPrices = append(notExpiredPrices, types.NewCurrentPrice(v.MarketID, v.Price)) notExpiredPrices = append(notExpiredPrices, types.NewCurrentPrice(v.MarketID, v.Price))
} }
} }
if len(notExpiredPrices) == 0 { if len(notExpiredPrices) == 0 {
store := ctx.KVStore(k.key) // NOTE: The current price stored will continue storing the most recent (expired)
store.Set( // price if this is not set.
types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), // This zero's out the current price stored value for that market and ensures
) // that CDP methods that GetCurrentPrice will return error.
k.setCurrentPrice(ctx, marketID, types.CurrentPrice{})
return types.ErrNoValidPrice return types.ErrNoValidPrice
} }
medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices) medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices)
// check case that market price was not set in genesis // check case that market price was not set in genesis
if validPrevPrice { if validPrevPrice && !medianPrice.Equal(prevPrice.Price) {
// only emit event if price has changed // only emit event if price has changed
if !medianPrice.Equal(prevPrice.Price) {
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeMarketPriceUpdated, types.EventTypeMarketPriceUpdated,
@ -133,18 +142,18 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error {
), ),
) )
} }
}
store := ctx.KVStore(k.key)
currentPrice := types.NewCurrentPrice(marketID, medianPrice) currentPrice := types.NewCurrentPrice(marketID, medianPrice)
k.setCurrentPrice(ctx, marketID, currentPrice)
store.Set(
types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(currentPrice),
)
return nil return nil
} }
func (k Keeper) setCurrentPrice(ctx sdk.Context, marketID string, currentPrice types.CurrentPrice) {
store := ctx.KVStore(k.key)
store.Set(types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(currentPrice))
}
// CalculateMedianPrice calculates the median prices for the input prices. // CalculateMedianPrice calculates the median prices for the input prices.
func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec { func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec {
l := len(prices) l := len(prices)

View File

@ -24,22 +24,23 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
var voteInfos VoteInfos var voteInfos VoteInfos
voteInfos = req.LastCommitInfo.GetVotes() voteInfos = req.LastCommitInfo.GetVotes()
validatorVestingKeys := k.GetAllAccountKeys(ctx) validatorVestingKeys := k.GetAllAccountKeys(ctx)
for _, key := range validatorVestingKeys { for _, key := range validatorVestingKeys {
acc := k.GetAccountFromAuthKeeper(ctx, key) acc := k.GetAccountFromAuthKeeper(ctx, key)
if k.AccountIsVesting(ctx, acc.GetAddress()) { if !k.AccountIsVesting(ctx, acc.GetAddress()) {
continue
}
vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress)
if !found || !vote.SignedLastBlock {
if ctx.BlockHeight() <= 1 { var missedBlock bool
// don't count missed blocks on block 1 since there is no vote history
k.UpdateMissingSignCount(ctx, acc.GetAddress(), false)
} else {
// if the validator was not found or explicitly didn't sign, increment the missing sign count // if the validator was not found or explicitly didn't sign, increment the missing sign count
k.UpdateMissingSignCount(ctx, acc.GetAddress(), true) if (!found || !vote.SignedLastBlock) && ctx.BlockHeight() > 1 {
} missedBlock = true
} else {
k.UpdateMissingSignCount(ctx, acc.GetAddress(), false)
} }
k.UpdateMissingSignCount(ctx, acc.GetAddress(), missedBlock)
// check if a period ended in the last block // check if a period ended in the last block
endTimes := k.GetPeriodEndTimes(ctx, key) endTimes := k.GetPeriodEndTimes(ctx, key)
@ -51,7 +52,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
// handle any new/remaining debt on the account // handle any new/remaining debt on the account
k.HandleVestingDebt(ctx, key, currentBlockTime) k.HandleVestingDebt(ctx, key, currentBlockTime)
} }
}
k.SetPreviousBlockTime(ctx, currentBlockTime) k.SetPreviousBlockTime(ctx, currentBlockTime)
} }

View File

@ -74,7 +74,7 @@ func TestBeginBlockerZeroHeight(t *testing.T) {
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// require missed block counter doesn't increment because there's no voting history // require missed block counter doesn't increment because there's no voting history
require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress) require.Equal(t, types.CurrentPeriodProgress{MissedBlocks: 0, TotalBlocks: 1}, vva.CurrentPeriodProgress)
// mark the validator as having missed // mark the validator as having missed
req = abci.RequestBeginBlock{ req = abci.RequestBeginBlock{
@ -90,7 +90,7 @@ func TestBeginBlockerZeroHeight(t *testing.T) {
BeginBlocker(ctx, req, vvk) BeginBlocker(ctx, req, vvk)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{0, 2}, vva.CurrentPeriodProgress) require.Equal(t, types.CurrentPeriodProgress{MissedBlocks: 0, TotalBlocks: 2}, vva.CurrentPeriodProgress)
} }
func TestBeginBlockerSignedBlock(t *testing.T) { func TestBeginBlockerSignedBlock(t *testing.T) {