From bfb9f7e20ab6c089037b5aff52bc62b0635f22ac Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Wed, 17 Jun 2020 11:09:44 +0200 Subject: [PATCH] cleanup begin/end block (#553) * cleanup begin/end block * update cdp BeginBlocker * update dist mint * fix tests * typo * add comment on CloseExpiredAuctions * fix tests --- x/auction/abci.go | 8 +++- x/auction/keeper/auctions.go | 72 +++++++++++++++----------------- x/cdp/abci.go | 13 ++++-- x/cdp/keeper/auctions.go | 7 +++- x/cdp/keeper/fees.go | 13 +++--- x/cdp/keeper/savings.go | 8 ++++ x/cdp/keeper/seize.go | 16 +++---- x/committee/abci.go | 2 - x/committee/keeper/keeper.go | 2 - x/committee/keeper/proposal.go | 3 +- x/incentive/keeper/rewards.go | 6 ++- x/kavadist/keeper/mint.go | 34 ++++++++------- x/pricefeed/abci.go | 23 ++++------ x/pricefeed/keeper/keeper.go | 47 ++++++++++++--------- x/validator-vesting/abci.go | 49 +++++++++++----------- x/validator-vesting/abci_test.go | 4 +- 16 files changed, 166 insertions(+), 141 deletions(-) diff --git a/x/auction/abci.go b/x/auction/abci.go index f1b4cfad..0470de77 100644 --- a/x/auction/abci.go +++ b/x/auction/abci.go @@ -1,13 +1,17 @@ package auction import ( + "errors" + 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) { err := k.CloseExpiredAuctions(ctx) - if err != nil { + if err != nil && !errors.Is(err, types.ErrAuctionNotFound) { panic(err) } } diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 11cadff9..392cdd29 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -1,6 +1,7 @@ package keeper import ( + "errors" "fmt" "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. func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) error { - auction, found := k.GetAuction(ctx, auctionID) if !found { 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 + var err error switch auc := auction.(type) { case types.SurplusAuction: - if err := k.PayoutSurplusAuction(ctx, auc); err != nil { - return err - } + err = k.PayoutSurplusAuction(ctx, auc) case types.DebtAuction: - if err := k.PayoutDebtAuction(ctx, auc); err != nil { - return err - } + err = k.PayoutDebtAuction(ctx, auc) case types.CollateralAuction: - if err := k.PayoutCollateralAuction(ctx, auc); err != nil { - return err - } + err = k.PayoutCollateralAuction(ctx, auc) default: - return sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType()) + err = sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType()) + } + + if err != nil { + return err } k.DeleteAuction(ctx, auctionID) @@ -502,23 +501,17 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, auction types.DebtAuction) er return err } // if there is remaining debt, return it to the calling module to manage - if auction.CorrespondingDebt.IsPositive() { - err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, auction.Initiator, sdk.NewCoins(auction.CorrespondingDebt)) - if err != nil { - return err - } + if !auction.CorrespondingDebt.IsPositive() { + 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. 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 - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, auction.Bidder, sdk.NewCoins(auction.Lot)) - if err != nil { - return err - } - return nil + return k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, auction.Bidder, sdk.NewCoins(auction.Lot)) } // 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 auction.CorrespondingDebt.IsPositive() { - err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, auction.Initiator, sdk.NewCoins(auction.CorrespondingDebt)) - if err != nil { - return err - } + if !auction.CorrespondingDebt.IsPositive() { + 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 { - var expiredAuctions []uint64 - k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { - expiredAuctions = append(expiredAuctions, id) + var err error + k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) (stop bool) { + 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 }) - // 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 nil + + return err } // earliestTime returns the earliest of two times. diff --git a/x/cdp/abci.go b/x/cdp/abci.go index e74fccb1..7bdfac8e 100644 --- a/x/cdp/abci.go +++ b/x/cdp/abci.go @@ -1,9 +1,13 @@ package cdp import ( + "errors" + sdk "github.com/cosmos/cosmos-sdk/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 @@ -17,7 +21,6 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { } for _, cp := range params.CollateralParams { - ok := k.UpdatePricefeedStatus(ctx, cp.SpotMarketID) if !ok { continue @@ -29,26 +32,30 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { } err := k.UpdateFeesForAllCdps(ctx, cp.Denom) - if err != nil { panic(err) } + err = k.LiquidateCdps(ctx, cp.LiquidationMarketID, cp.Denom, cp.LiquidationRatio) - if err != nil { + if err != nil && !errors.Is(err, pricefeedtypes.ErrNoValidPrice) { panic(err) } } + err := k.RunSurplusAndDebtAuctions(ctx) if err != nil { panic(err) } + distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix()) if !distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) { return } + err = k.DistributeSavingsRate(ctx, params.DebtParam.Denom) if err != nil { panic(err) } + k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime()) } diff --git a/x/cdp/keeper/auctions.go b/x/cdp/keeper/auctions.go index ab80f8d7..18395b56 100644 --- a/x/cdp/keeper/auctions.go +++ b/x/cdp/keeper/auctions.go @@ -149,11 +149,13 @@ func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) error { } remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc) params := k.GetParams(ctx) + if remainingDebt.GTE(params.DebtAuctionThreshold) { debtLot := sdk.NewCoin(k.GetDebtDenom(ctx), params.DebtAuctionLot) bidCoin := sdk.NewCoin(params.DebtParam.Denom, debtLot.Amount) - _, err := k.auctionKeeper.StartDebtAuction( - ctx, types.LiquidatorMacc, bidCoin, sdk.NewCoin(k.GetGovDenom(ctx), debtLot.Amount.Mul(sdk.NewInt(dump))), debtLot) + initialLot := sdk.NewCoin(k.GetGovDenom(ctx), debtLot.Amount.Mul(sdk.NewInt(dump))) + + _, err := k.auctionKeeper.StartDebtAuction(ctx, types.LiquidatorMacc, bidCoin, initialLot, debtLot) if err != nil { return err } @@ -163,6 +165,7 @@ func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) error { if !surplus.GTE(params.SurplusAuctionThreshold) { return nil } + surplusLot := sdk.NewCoin(params.DebtParam.Denom, sdk.MinInt(params.SurplusAuctionLot, surplus)) _, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx)) return err diff --git a/x/cdp/keeper/fees.go b/x/cdp/keeper/fees.go index 1cd98ffa..3e1a5500 100644 --- a/x/cdp/keeper/fees.go +++ b/x/cdp/keeper/fees.go @@ -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 { var iterationErr error k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool { - 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())) 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())) { return false } + // mint debt coins to the cdp account err := k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees) if err != nil { iterationErr = err return true } + previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom) newDebt := previousDebt.Add(newFees.Amount) 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 = total.Add(principal.Amount) k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total) - } // 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) { total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom) - total = total.Sub(principal.Amount) - if total.IsNegative() { - // can happen in tests due to rounding errors in fee calculation - total = sdk.ZeroInt() - } + // NOTE: negative total principal can happen in tests due to rounding errors + // in fee calculation + total = sdk.MaxInt(total.Sub(principal.Amount), sdk.ZeroInt()) k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total) } diff --git a/x/cdp/keeper/savings.go b/x/cdp/keeper/savings.go index 37dda6ec..0a443188 100644 --- a/x/cdp/keeper/savings.go +++ b/x/cdp/keeper/savings.go @@ -18,6 +18,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error { if !found { return sdkerrors.Wrap(types.ErrDebtNotSupported, debtDenom) } + savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc) surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom) if surplusToDistribute.IsZero() { @@ -26,21 +27,26 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error { 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() @@ -51,6 +57,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error { if !interest.IsPositive() { return false } + interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest)) err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins) if err != nil { @@ -60,6 +67,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error { surplusToDistribute = surplusToDistribute.Sub(interest) return false }) + return iterationErr } diff --git a/x/cdp/keeper/seize.go b/x/cdp/keeper/seize.go index 1e64360c..e67740df 100644 --- a/x/cdp/keeper/seize.go +++ b/x/cdp/keeper/seize.go @@ -24,9 +24,7 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error { deposits := k.GetDeposits(ctx, cdp.ID) debt := cdp.Principal.Amount.Add(cdp.AccumulatedFees.Amount) modAccountDebt := k.getModAccountDebt(ctx, types.ModuleName) - if modAccountDebt.LT(debt) { - debt = modAccountDebt - } + debt = sdk.MinInt(debt, modAccountDebt) debtCoin := sdk.NewCoin(k.GetDebtDenom(ctx), debt) err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(debtCoin)) 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 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( sdk.NewEvent( types.EventTypeCdpLiquidation, @@ -43,12 +47,8 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error { 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) if err != nil { return err diff --git a/x/committee/abci.go b/x/committee/abci.go index 215aaa42..bbf7e84b 100644 --- a/x/committee/abci.go +++ b/x/committee/abci.go @@ -8,9 +8,7 @@ import ( // BeginBlocker runs at the start of every block. 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 k.EnactPassedProposals(ctx) - k.CloseExpiredProposals(ctx) } diff --git a/x/committee/keeper/keeper.go b/x/committee/keeper/keeper.go index 4e9de243..d66356c9 100644 --- a/x/committee/keeper/keeper.go +++ b/x/committee/keeper/keeper.go @@ -207,9 +207,7 @@ func (k Keeper) GetProposalsByCommittee(ctx sdk.Context, committeeID uint64) []t // DeleteProposalAndVotes removes a proposal and its associated votes. func (k Keeper) DeleteProposalAndVotes(ctx sdk.Context, proposalID uint64) { - votes := k.GetVotesByProposal(ctx, proposalID) - k.DeleteProposal(ctx, proposalID) for _, v := range votes { k.DeleteVote(ctx, v.ProposalID, v.Voter) diff --git a/x/committee/keeper/proposal.go b/x/committee/keeper/proposal.go index 017ef589..204fbd76 100644 --- a/x/committee/keeper/proposal.go +++ b/x/committee/keeper/proposal.go @@ -138,7 +138,9 @@ func (k Keeper) EnactPassedProposals(ctx sdk.Context) { if err != nil { panic(err) } + if !passes { + // continue to next proposal return false } @@ -164,7 +166,6 @@ func (k Keeper) EnactPassedProposals(ctx sdk.Context) { // CloseExpiredProposals removes proposals (and associated votes) that have past their deadline. func (k Keeper) CloseExpiredProposals(ctx sdk.Context) { - k.IterateProposals(ctx, func(proposal types.Proposal) bool { if !proposal.HasExpiredBy(ctx.BlockTime()) { return false diff --git a/x/incentive/keeper/rewards.go b/x/incentive/keeper/rewards.go index d8c2054b..c07a9f1a 100644 --- a/x/incentive/keeper/rewards.go +++ b/x/incentive/keeper/rewards.go @@ -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) { previousBlockTime, found := k.GetPreviousBlockTime(ctx) if !found { @@ -56,6 +57,7 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) { k.SetPreviousBlockTime(ctx, previousBlockTime) return } + k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool { expired := false // 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()) expired = true } + // the amount of rewards to pay (rewardAmount * timeElapsed) rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed) id := k.GetNextClaimPeriodID(ctx, rp.Denom) @@ -85,6 +88,7 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) { k.HandleRewardPeriodExpiry(ctx, rp) return false }) + k.SetPreviousBlockTime(ctx, ctx.BlockTime()) } diff --git a/x/kavadist/keeper/mint.go b/x/kavadist/keeper/mint.go index c7ae61dc..98df1181 100644 --- a/x/kavadist/keeper/mint.go +++ b/x/kavadist/keeper/mint.go @@ -19,42 +19,44 @@ func (k Keeper) MintPeriodInflation(ctx sdk.Context) error { ) return nil } + previousBlockTime, found := k.GetPreviousBlockTime(ctx) if !found { previousBlockTime = ctx.BlockTime() k.SetPreviousBlockTime(ctx, previousBlockTime) return nil } + + var err error for _, period := range params.Periods { + switch { // Case 1 - period is fully expired - if period.End.Before(previousBlockTime) { + case period.End.Before(previousBlockTime): continue - } + // 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 timeElapsed := sdk.NewInt(period.End.Unix() - previousBlockTime.Unix()) - err := k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom) - if err != nil { - return err - } + err = k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom) // 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 previousBlockTime = period.End - } + // 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 timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix()) - err := k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom) - if err != nil { - return err - } - } + err = k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom) + // Case 4 - period hasn't started - if period.Start.After(ctx.BlockTime()) || period.Start.Equal(ctx.BlockTime()) { + case period.Start.After(ctx.BlockTime()) || period.Start.Equal(ctx.BlockTime()): continue } + + if err != nil { + return err + } } k.SetPreviousBlockTime(ctx, ctx.BlockTime()) return nil @@ -78,11 +80,13 @@ func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, ti if err != nil { return err } + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeKavaDist, sdk.NewAttribute(types.AttributeKeyInflation, sdk.NewCoin(denom, amountToMint).String()), ), ) + return nil } diff --git a/x/pricefeed/abci.go b/x/pricefeed/abci.go index 49623353..a9762d07 100644 --- a/x/pricefeed/abci.go +++ b/x/pricefeed/abci.go @@ -1,28 +1,23 @@ package pricefeed import ( - "fmt" + "errors" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/pricefeed/types" ) // EndBlocker updates the current pricefeed func EndBlocker(ctx sdk.Context, k Keeper) { // Update the current price of each asset. for _, market := range k.GetMarkets(ctx) { - 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 - } + if !market.Active { + continue + } + + err := k.SetCurrentPrices(ctx, market.MarketID) + if err != nil && !errors.Is(err, types.ErrNoValidPrice) { + panic(err) } } - return } diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index c25e08f2..3a3c0a57 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -1,9 +1,12 @@ package keeper import ( + "fmt" "sort" "time" + "github.com/tendermint/tendermint/libs/log" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" 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 func (k Keeper) SetPrice( ctx sdk.Context, @@ -111,40 +119,41 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error { notExpiredPrices = append(notExpiredPrices, types.NewCurrentPrice(v.MarketID, v.Price)) } } + if len(notExpiredPrices) == 0 { - store := ctx.KVStore(k.key) - store.Set( - types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), - ) + // NOTE: The current price stored will continue storing the most recent (expired) + // price if this is not set. + // 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 } medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices) // 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 - if !medianPrice.Equal(prevPrice.Price) { - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeMarketPriceUpdated, - sdk.NewAttribute(types.AttributeMarketID, marketID), - sdk.NewAttribute(types.AttributeMarketPrice, medianPrice.String()), - ), - ) - } + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeMarketPriceUpdated, + sdk.NewAttribute(types.AttributeMarketID, marketID), + sdk.NewAttribute(types.AttributeMarketPrice, medianPrice.String()), + ), + ) } - store := ctx.KVStore(k.key) currentPrice := types.NewCurrentPrice(marketID, medianPrice) - - store.Set( - types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(currentPrice), - ) + k.setCurrentPrice(ctx, marketID, currentPrice) 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. func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec { l := len(prices) diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index 97a976e4..ac714946 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -24,34 +24,35 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) var voteInfos VoteInfos voteInfos = req.LastCommitInfo.GetVotes() validatorVestingKeys := k.GetAllAccountKeys(ctx) + for _, key := range validatorVestingKeys { acc := k.GetAccountFromAuthKeeper(ctx, key) - if k.AccountIsVesting(ctx, acc.GetAddress()) { - vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) - if !found || !vote.SignedLastBlock { - if ctx.BlockHeight() <= 1 { - // 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 - k.UpdateMissingSignCount(ctx, acc.GetAddress(), true) - } - } else { - k.UpdateMissingSignCount(ctx, acc.GetAddress(), false) - } - - // check if a period ended in the last block - endTimes := k.GetPeriodEndTimes(ctx, key) - - for i, t := range endTimes { - if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { - k.UpdateVestedCoinsProgress(ctx, key, i) - } - } - // handle any new/remaining debt on the account - k.HandleVestingDebt(ctx, key, currentBlockTime) + if !k.AccountIsVesting(ctx, acc.GetAddress()) { + continue } + + vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) + + var missedBlock bool + // if the validator was not found or explicitly didn't sign, increment the missing sign count + if (!found || !vote.SignedLastBlock) && ctx.BlockHeight() > 1 { + missedBlock = true + } + + k.UpdateMissingSignCount(ctx, acc.GetAddress(), missedBlock) + + // check if a period ended in the last block + endTimes := k.GetPeriodEndTimes(ctx, key) + + for i, t := range endTimes { + if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { + k.UpdateVestedCoinsProgress(ctx, key, i) + } + } + // handle any new/remaining debt on the account + k.HandleVestingDebt(ctx, key, currentBlockTime) } + k.SetPreviousBlockTime(ctx, currentBlockTime) } diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index a423740a..60d3a050 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -74,7 +74,7 @@ func TestBeginBlockerZeroHeight(t *testing.T) { vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) // 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 req = abci.RequestBeginBlock{ @@ -90,7 +90,7 @@ func TestBeginBlockerZeroHeight(t *testing.T) { BeginBlocker(ctx, req, vvk) 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) {