mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-27 16:55:21 +00:00
c63ecf908a
* Add 'InterestFactor' to CDP type (#734) * update cdp type to include interest factor * fix build * Add cdp accumulator methods (#735) * remame fees to interest * add accumulate interest method * add basic test * add note * address review comments * update tests * Add sync cdp interest method (#737) * remame fees to interest * add accumulate interest method * add basic test * add note * address review comments * update tests * remove old fee functions * add method to synchronize cdp interest * add multi-cdp tests * add test with many blocks * add test for interest getter * address review comments * calculate time difference then convert to seconds * fix: update collateral index when syncing interest * fix: differentiate between case when apy is zero and all fees are being rounded to zero * fix: round time difference properly * update cdp genesis state and migrations (#738) * remame fees to interest * add accumulate interest method * add basic test * add note * address review comments * update tests * remove old fee functions * add method to synchronize cdp interest * add multi-cdp tests * add test with many blocks * add test for interest getter * update cdp genesis state and migrations * address review comments * calculate time difference then convert to seconds * fix: update collateral index when syncing interest * fix: differentiate between case when apy is zero and all fees are being rounded to zero * fix: simplify add/remove/update collateral index * update genesis state to include total principal amounts * update migration * Delete kava-4-cdp-state-block-500000.json * Add cdp liquidations by external keeper (#750) * feat: split liquidations between external keepers and automated begin blocker * address review comments * USDX incentive accumulators (#752) * feat: split liquidations between external keepers and automated begin blocker * wip: refactor usdx minting incentives to use accumulators/hooks * wip: refactor usdx minting claim object * feat: use accumulators/hooks for usdx minting rewards * fix: get tests passing * fix: don't create claim objects unless that cdp type is eligable for rewards * add begin blocker * update client * cleanup comments/tests * update querier * address review comments * fix: check for division by zero * address review comments * run hook before interest is synced * Remove savings rate (#764) * remove savings rate * remove savings rate from debt param * update migrations * address review comments * Add usdx incentives calculation test (#765) * add usdx incentive calculation test * update reward calculation * add allowable error to test criteria * Update x/incentive/keeper/rewards_test.go Co-authored-by: Kevin Davis <karzak@users.noreply.github.com> * fix: remove old fields from test genesis state Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com> Co-authored-by: Ruaridh <rhuairahrighairidh@users.noreply.github.com>
245 lines
8.1 KiB
Go
245 lines
8.1 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
|
|
"github.com/kava-labs/kava/x/cdp/types"
|
|
)
|
|
|
|
// AddPrincipal adds debt to a cdp if the additional debt does not put the cdp below the liquidation ratio
|
|
func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateralType string, principal sdk.Coin) error {
|
|
// validation
|
|
cdp, found := k.GetCdpByOwnerAndCollateralType(ctx, owner, collateralType)
|
|
if !found {
|
|
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, collateralType)
|
|
}
|
|
err := k.ValidatePrincipalDraw(ctx, principal, cdp.Principal.Denom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = k.ValidateDebtLimit(ctx, cdp.Type, principal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
k.hooks.BeforeCDPModified(ctx, cdp)
|
|
cdp = k.SynchronizeInterest(ctx, cdp)
|
|
|
|
err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Type, cdp.Principal.Add(principal), cdp.AccumulatedFees)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// mint the principal and send it to the cdp owner
|
|
err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(principal))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, owner, sdk.NewCoins(principal))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// mint the corresponding amount of debt coins in the cdp module account
|
|
err = k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), principal)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// emit cdp draw event
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeCdpDraw,
|
|
sdk.NewAttribute(sdk.AttributeKeyAmount, principal.String()),
|
|
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
|
|
),
|
|
)
|
|
|
|
// update cdp state
|
|
cdp.Principal = cdp.Principal.Add(principal)
|
|
|
|
// increment total principal for the input collateral type
|
|
k.IncrementTotalPrincipal(ctx, cdp.Type, principal)
|
|
|
|
// set cdp state and indexes in the store
|
|
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
|
return k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
|
}
|
|
|
|
// RepayPrincipal removes debt from the cdp
|
|
// If all debt is repaid, the collateral is returned to depositors and the cdp is removed from the store
|
|
func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, collateralType string, payment sdk.Coin) error {
|
|
// validation
|
|
cdp, found := k.GetCdpByOwnerAndCollateralType(ctx, owner, collateralType)
|
|
if !found {
|
|
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, collateralType)
|
|
}
|
|
|
|
err := k.ValidatePaymentCoins(ctx, cdp, payment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = k.ValidateBalance(ctx, payment, owner)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
k.hooks.BeforeCDPModified(ctx, cdp)
|
|
cdp = k.SynchronizeInterest(ctx, cdp)
|
|
|
|
// Note: assumes cdp.Principal and cdp.AccumulatedFees don't change during calculations
|
|
totalPrincipal := cdp.GetTotalPrincipal()
|
|
|
|
// calculate fee and principal payment
|
|
feePayment, principalPayment := k.calculatePayment(ctx, totalPrincipal, cdp.AccumulatedFees, payment)
|
|
|
|
err = k.validatePrincipalPayment(ctx, cdp, principalPayment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// send the payment from the sender to the cpd module
|
|
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, sdk.NewCoins(feePayment.Add(principalPayment)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// burn the payment coins
|
|
err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(feePayment.Add(principalPayment)))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// burn the corresponding amount of debt coins
|
|
cdpDebt := k.getModAccountDebt(ctx, types.ModuleName)
|
|
paymentAmount := feePayment.Add(principalPayment).Amount
|
|
|
|
debtDenom := k.GetDebtDenom(ctx)
|
|
coinsToBurn := sdk.NewCoin(debtDenom, paymentAmount)
|
|
|
|
if paymentAmount.GT(cdpDebt) {
|
|
coinsToBurn = sdk.NewCoin(debtDenom, cdpDebt)
|
|
}
|
|
|
|
err = k.BurnDebtCoins(ctx, types.ModuleName, debtDenom, coinsToBurn)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// emit repayment event
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeCdpRepay,
|
|
sdk.NewAttribute(sdk.AttributeKeyAmount, feePayment.Add(principalPayment).String()),
|
|
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
|
|
),
|
|
)
|
|
|
|
// remove the old collateral:debt ratio index
|
|
|
|
// update cdp state
|
|
if !principalPayment.IsZero() {
|
|
cdp.Principal = cdp.Principal.Sub(principalPayment)
|
|
}
|
|
cdp.AccumulatedFees = cdp.AccumulatedFees.Sub(feePayment)
|
|
|
|
// decrement the total principal for the input collateral type
|
|
k.DecrementTotalPrincipal(ctx, cdp.Type, feePayment.Add(principalPayment))
|
|
|
|
// if the debt is fully paid, return collateral to depositors,
|
|
// and remove the cdp and indexes from the store
|
|
if cdp.Principal.IsZero() && cdp.AccumulatedFees.IsZero() {
|
|
k.ReturnCollateral(ctx, cdp)
|
|
k.RemoveCdpOwnerIndex(ctx, cdp)
|
|
err := k.DeleteCdpAndCollateralRatioIndex(ctx, cdp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// emit cdp close event
|
|
ctx.EventManager().EmitEvent(
|
|
sdk.NewEvent(
|
|
types.EventTypeCdpClose,
|
|
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
|
|
),
|
|
)
|
|
return nil
|
|
}
|
|
|
|
// set cdp state and update indexes
|
|
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
|
|
return k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
|
|
}
|
|
|
|
// ValidatePaymentCoins validates that the input coins are valid for repaying debt
|
|
func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk.Coin) error {
|
|
debt := cdp.GetTotalPrincipal()
|
|
if payment.Denom != debt.Denom {
|
|
return sdkerrors.Wrapf(types.ErrInvalidPayment, "cdp %d: expected %s, got %s", cdp.ID, debt.Denom, payment.Denom)
|
|
}
|
|
_, found := k.GetDebtParam(ctx, payment.Denom)
|
|
if !found {
|
|
return sdkerrors.Wrapf(types.ErrInvalidPayment, "payment denom %s not found", payment.Denom)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReturnCollateral returns collateral to depositors on a cdp and removes deposits from the store
|
|
func (k Keeper) ReturnCollateral(ctx sdk.Context, cdp types.CDP) {
|
|
deposits := k.GetDeposits(ctx, cdp.ID)
|
|
for _, deposit := range deposits {
|
|
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, deposit.Depositor, sdk.NewCoins(deposit.Amount))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
k.DeleteDeposit(ctx, cdp.ID, deposit.Depositor)
|
|
}
|
|
}
|
|
|
|
// calculatePayment divides the input payment into the portions that will be used to repay fees and principal
|
|
// owed - Principal + AccumulatedFees
|
|
// fees - AccumulatedFees
|
|
// CONTRACT: owned and payment denoms must be checked before calling this function.
|
|
func (k Keeper) calculatePayment(ctx sdk.Context, owed, fees, payment sdk.Coin) (sdk.Coin, sdk.Coin) {
|
|
// divides repayment into principal and fee components, with fee payment applied first.
|
|
|
|
feePayment := sdk.NewCoin(payment.Denom, sdk.ZeroInt())
|
|
principalPayment := sdk.NewCoin(payment.Denom, sdk.ZeroInt())
|
|
var overpayment sdk.Coin
|
|
// return zero value coins if payment amount is invalid
|
|
if !payment.Amount.IsPositive() {
|
|
return feePayment, principalPayment
|
|
}
|
|
// check for over payment
|
|
if payment.Amount.GT(owed.Amount) {
|
|
overpayment = payment.Sub(owed)
|
|
payment = payment.Sub(overpayment)
|
|
}
|
|
// if no fees, 100% of payment is principal payment
|
|
if fees.IsZero() {
|
|
return feePayment, payment
|
|
}
|
|
// pay fees before repaying principal
|
|
if payment.Amount.GT(fees.Amount) {
|
|
feePayment = fees
|
|
principalPayment = payment.Sub(fees)
|
|
} else {
|
|
feePayment = payment
|
|
}
|
|
return feePayment, principalPayment
|
|
}
|
|
|
|
// validatePrincipalPayment checks that the payment is either full or does not put the cdp below the debt floor
|
|
// CONTRACT: payment denom must be checked before calling this function.
|
|
func (k Keeper) validatePrincipalPayment(ctx sdk.Context, cdp types.CDP, payment sdk.Coin) error {
|
|
proposedBalance := cdp.Principal.Amount.Sub(payment.Amount)
|
|
dp, _ := k.GetDebtParam(ctx, payment.Denom)
|
|
if proposedBalance.GT(sdk.ZeroInt()) && proposedBalance.LT(dp.DebtFloor) {
|
|
return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", sdk.NewCoin(payment.Denom, proposedBalance), dp.DebtFloor)
|
|
}
|
|
return nil
|
|
}
|