R4R: Move liquidator functions to cdp module (#280)

* wip: tpyes and keeper methods

* wip: iterators

* wip: types and keeper methods

* wip: add msgs

* wip: client methods

* wip: rebase develop

* wip: types tests

* wip: keeper tests, small fixes

* wip: add cdp tests

* wip: deposit tests

* wip: keeper tests

* wip: tests and module methods

* feat: error when fetching expired price

* feat: conversion factor for external assets

* feat: debt floor for new cdps

* feat: save deposits on export genesis

* feat: ensure messages implement msg

* feat: index deposits by status

* fix: stray comment

* wip: address review comments

* address review comments

* wip: move liquidation to cdp module

* wip: handle liquidations directly

* wip: use new auction interface

* feat: auction collateral in cdp begin block

* feat: update param validation

* feat: surplus and debt auctions

* address review comments

* address review comments

* fix: auction multiple deposits

* clean up netting function
This commit is contained in:
Kevin Davis 2020-01-15 15:19:33 +01:00 committed by GitHub
parent ba80b508ab
commit 9b1bf55be7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 706 additions and 336 deletions

3
.gitignore vendored
View File

@ -14,3 +14,6 @@
# Exclude build files # Exclude build files
vendor vendor
build build
# IDE files
*.vscode

View File

@ -229,18 +229,19 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
pricefeedSubspace, pricefeedSubspace,
pricefeed.DefaultCodespace) pricefeed.DefaultCodespace)
// NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType) // NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType)
app.cdpKeeper = cdp.NewKeeper(
app.cdc,
keys[cdp.StoreKey],
cdpSubspace,
app.pricefeedKeeper,
app.supplyKeeper,
cdp.DefaultCodespace)
app.auctionKeeper = auction.NewKeeper( app.auctionKeeper = auction.NewKeeper(
app.cdc, app.cdc,
keys[auction.StoreKey], keys[auction.StoreKey],
app.supplyKeeper, app.supplyKeeper,
auctionSubspace) auctionSubspace)
app.cdpKeeper = cdp.NewKeeper(
app.cdc,
keys[cdp.StoreKey],
cdpSubspace,
app.pricefeedKeeper,
app.auctionKeeper,
app.supplyKeeper,
cdp.DefaultCodespace)
// register the staking hooks // register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks

View File

@ -53,20 +53,20 @@ func NewTestApp() TestApp {
return TestApp{App: *app} return TestApp{App: *app}
} }
func (tApp TestApp) GetAccountKeeper() auth.AccountKeeper { return tApp.accountKeeper } func (tApp TestApp) GetAccountKeeper() auth.AccountKeeper { return tApp.accountKeeper }
func (tApp TestApp) GetBankKeeper() bank.Keeper { return tApp.bankKeeper } func (tApp TestApp) GetBankKeeper() bank.Keeper { return tApp.bankKeeper }
func (tApp TestApp) GetSupplyKeeper() supply.Keeper { return tApp.supplyKeeper } func (tApp TestApp) GetSupplyKeeper() supply.Keeper { return tApp.supplyKeeper }
func (tApp TestApp) GetStakingKeeper() staking.Keeper { return tApp.stakingKeeper } func (tApp TestApp) GetStakingKeeper() staking.Keeper { return tApp.stakingKeeper }
func (tApp TestApp) GetSlashingKeeper() slashing.Keeper { return tApp.slashingKeeper } func (tApp TestApp) GetSlashingKeeper() slashing.Keeper { return tApp.slashingKeeper }
func (tApp TestApp) GetMintKeeper() mint.Keeper { return tApp.mintKeeper } func (tApp TestApp) GetMintKeeper() mint.Keeper { return tApp.mintKeeper }
func (tApp TestApp) GetDistrKeeper() distribution.Keeper { return tApp.distrKeeper } func (tApp TestApp) GetDistrKeeper() distribution.Keeper { return tApp.distrKeeper }
func (tApp TestApp) GetGovKeeper() gov.Keeper { return tApp.govKeeper } func (tApp TestApp) GetGovKeeper() gov.Keeper { return tApp.govKeeper }
func (tApp TestApp) GetCrisisKeeper() crisis.Keeper { return tApp.crisisKeeper } func (tApp TestApp) GetCrisisKeeper() crisis.Keeper { return tApp.crisisKeeper }
func (tApp TestApp) GetParamsKeeper() params.Keeper { return tApp.paramsKeeper } func (tApp TestApp) GetParamsKeeper() params.Keeper { return tApp.paramsKeeper }
func (tApp TestApp) GetVVKeeper() validatorvesting.Keeper { return tApp.vvKeeper } func (tApp TestApp) GetVVKeeper() validatorvesting.Keeper { return tApp.vvKeeper }
func (tApp TestApp) GetAuctionKeeper() auction.Keeper { return tApp.auctionKeeper } func (tApp TestApp) GetAuctionKeeper() auction.Keeper { return tApp.auctionKeeper }
func (tApp TestApp) GetCDPKeeper() cdp.Keeper { return tApp.cdpKeeper } func (tApp TestApp) GetCDPKeeper() cdp.Keeper { return tApp.cdpKeeper }
func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper } func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper }
// This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states // This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states
func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp { func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp {

View File

@ -23,6 +23,9 @@ type Keeper struct {
// NewKeeper returns a new auction keeper. // NewKeeper returns a new auction keeper.
func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper { func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper {
if addr := supplyKeeper.GetModuleAddress(types.ModuleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
}
return Keeper{ return Keeper{
supplyKeeper: supplyKeeper, supplyKeeper: supplyKeeper,
storeKey: storeKey, storeKey: storeKey,

View File

@ -7,6 +7,7 @@ import (
// SupplyKeeper defines the expected supply Keeper // SupplyKeeper defines the expected supply Keeper
type SupplyKeeper interface { type SupplyKeeper interface {
GetModuleAddress(name string) sdk.AccAddress
GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI
SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error

View File

@ -1,11 +1,14 @@
package cdp package cdp
import ( import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/cdp/types"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
) )
// BeginBlock 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
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
params := k.GetParams(ctx) params := k.GetParams(ctx)
previousBlockTime, found := k.GetPreviousBlockTime(ctx) previousBlockTime, found := k.GetPreviousBlockTime(ctx)
@ -18,7 +21,26 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
k.HandleNewDebt(ctx, cp.Denom, dp.Denom, timeElapsed) k.HandleNewDebt(ctx, cp.Denom, dp.Denom, timeElapsed)
} }
k.LiquidateCdps(ctx, cp.MarketID, cp.Denom, cp.LiquidationRatio) err := k.LiquidateCdps(ctx, cp.MarketID, cp.Denom, cp.LiquidationRatio)
if err != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeBeginBlockerFatal,
sdk.NewAttribute(sdk.AttributeKeyModule, fmt.Sprintf("%s", ModuleName)),
sdk.NewAttribute(types.AttributeKeyError, fmt.Sprintf("%s", err)),
),
)
}
}
err := k.RunSurplusAndDebtAuctions(ctx)
if err != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeBeginBlockerFatal,
sdk.NewAttribute(sdk.AttributeKeyModule, fmt.Sprintf("%s", ModuleName)),
sdk.NewAttribute(types.AttributeKeyError, fmt.Sprintf("%s", err)),
),
)
} }
k.SetPreviousBlockTime(ctx, ctx.BlockTime()) k.SetPreviousBlockTime(ctx, ctx.BlockTime())
return return

View File

@ -8,6 +8,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation" "github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/auction"
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
@ -116,7 +117,7 @@ func (suite *ModuleTestSuite) TestBeginBlock() {
btcLiquidations := int(seizedBtcCollateral.Quo(i(100000000)).Int64()) btcLiquidations := int(seizedBtcCollateral.Quo(i(100000000)).Int64())
suite.Equal(len(suite.liquidations.btc), btcLiquidations) suite.Equal(len(suite.liquidations.btc), btcLiquidations)
acc = sk.GetModuleAccount(suite.ctx, cdp.LiquidatorMacc) acc = sk.GetModuleAccount(suite.ctx, auction.ModuleName)
suite.Equal(suite.liquidations.debt, acc.GetCoins().AmountOf("debt").Int64()) suite.Equal(suite.liquidations.debt, acc.GetCoins().AmountOf("debt").Int64())
} }

View File

@ -11,8 +11,6 @@ import (
) )
const ( const (
StatusNil = types.StatusNil
StatusLiquidated = types.StatusLiquidated
DefaultCodespace = types.DefaultCodespace DefaultCodespace = types.DefaultCodespace
CodeCdpAlreadyExists = types.CodeCdpAlreadyExists CodeCdpAlreadyExists = types.CodeCdpAlreadyExists
CodeCollateralLengthInvalid = types.CodeCollateralLengthInvalid CodeCollateralLengthInvalid = types.CodeCollateralLengthInvalid
@ -36,15 +34,17 @@ const (
EventTypeCdpClose = types.EventTypeCdpClose EventTypeCdpClose = types.EventTypeCdpClose
EventTypeCdpWithdrawal = types.EventTypeCdpWithdrawal EventTypeCdpWithdrawal = types.EventTypeCdpWithdrawal
EventTypeCdpLiquidation = types.EventTypeCdpLiquidation EventTypeCdpLiquidation = types.EventTypeCdpLiquidation
EventTypeBeginBlockerFatal = types.EventTypeBeginBlockerFatal
AttributeKeyCdpID = types.AttributeKeyCdpID AttributeKeyCdpID = types.AttributeKeyCdpID
AttributeKeyDepositor = types.AttributeKeyDepositor AttributeKeyDepositor = types.AttributeKeyDepositor
AttributeValueCategory = types.AttributeValueCategory AttributeValueCategory = types.AttributeValueCategory
LiquidatorMacc = types.LiquidatorMacc AttributeKeyError = types.AttributeKeyError
ModuleName = types.ModuleName ModuleName = types.ModuleName
StoreKey = types.StoreKey StoreKey = types.StoreKey
RouterKey = types.RouterKey RouterKey = types.RouterKey
QuerierRoute = types.QuerierRoute QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace DefaultParamspace = types.DefaultParamspace
LiquidatorMacc = types.LiquidatorMacc
QueryGetCdp = types.QueryGetCdp QueryGetCdp = types.QueryGetCdp
QueryGetCdps = types.QueryGetCdps QueryGetCdps = types.QueryGetCdps
QueryGetCdpsByCollateralization = types.QueryGetCdpsByCollateralization QueryGetCdpsByCollateralization = types.QueryGetCdpsByCollateralization
@ -58,7 +58,6 @@ var (
// functions aliases // functions aliases
NewCDP = types.NewCDP NewCDP = types.NewCDP
RegisterCodec = types.RegisterCodec RegisterCodec = types.RegisterCodec
StatusFromByte = types.StatusFromByte
NewDeposit = types.NewDeposit NewDeposit = types.NewDeposit
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
@ -116,6 +115,7 @@ var (
CollateralRatioIndexPrefix = types.CollateralRatioIndexPrefix CollateralRatioIndexPrefix = types.CollateralRatioIndexPrefix
CdpIDKey = types.CdpIDKey CdpIDKey = types.CdpIDKey
DebtDenomKey = types.DebtDenomKey DebtDenomKey = types.DebtDenomKey
GovDenomKey = types.GovDenomKey
DepositKeyPrefix = types.DepositKeyPrefix DepositKeyPrefix = types.DepositKeyPrefix
PrincipalKeyPrefix = types.PrincipalKeyPrefix PrincipalKeyPrefix = types.PrincipalKeyPrefix
AccumulatorKeyPrefix = types.AccumulatorKeyPrefix AccumulatorKeyPrefix = types.AccumulatorKeyPrefix
@ -124,12 +124,17 @@ var (
KeyCollateralParams = types.KeyCollateralParams KeyCollateralParams = types.KeyCollateralParams
KeyDebtParams = types.KeyDebtParams KeyDebtParams = types.KeyDebtParams
KeyCircuitBreaker = types.KeyCircuitBreaker KeyCircuitBreaker = types.KeyCircuitBreaker
KeyDebtThreshold = types.KeyDebtThreshold
KeySurplusThreshold = types.KeySurplusThreshold
DefaultGlobalDebt = types.DefaultGlobalDebt DefaultGlobalDebt = types.DefaultGlobalDebt
DefaultCircuitBreaker = types.DefaultCircuitBreaker DefaultCircuitBreaker = types.DefaultCircuitBreaker
DefaultCollateralParams = types.DefaultCollateralParams DefaultCollateralParams = types.DefaultCollateralParams
DefaultDebtParams = types.DefaultDebtParams DefaultDebtParams = types.DefaultDebtParams
DefaultCdpStartingID = types.DefaultCdpStartingID DefaultCdpStartingID = types.DefaultCdpStartingID
DefaultDebtDenom = types.DefaultDebtDenom DefaultDebtDenom = types.DefaultDebtDenom
DefaultGovDenom = types.DefaultGovDenom
DefaultSurplusThreshold = types.DefaultSurplusThreshold
DefaultDebtThreshold = types.DefaultDebtThreshold
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
MaxSortableDec = types.MaxSortableDec MaxSortableDec = types.MaxSortableDec
) )
@ -138,7 +143,6 @@ type (
CDP = types.CDP CDP = types.CDP
CDPs = types.CDPs CDPs = types.CDPs
Deposit = types.Deposit Deposit = types.Deposit
DepositStatus = types.DepositStatus
Deposits = types.Deposits Deposits = types.Deposits
SupplyKeeper = types.SupplyKeeper SupplyKeeper = types.SupplyKeeper
PricefeedKeeper = types.PricefeedKeeper PricefeedKeeper = types.PricefeedKeeper

View File

@ -47,8 +47,9 @@ func QueryCdpCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return err return err
} }
collateralType := args[1] // TODO validation? collateralType := args[1] // TODO validation?
bz, err := cdc.MarshalJSON(types.QueryCdpsParams{ bz, err := cdc.MarshalJSON(types.QueryCdpParams{
CollateralDenom: collateralType, CollateralDenom: collateralType,
Owner: ownerAddress,
}) })
if err != nil { if err != nil {
return err return err
@ -58,8 +59,6 @@ func QueryCdpCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdp) route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdp)
res, _, err := cliCtx.QueryWithData(route, bz) res, _, err := cliCtx.QueryWithData(route, bz)
if err != nil { if err != nil {
fmt.Printf("error when getting cdp info - %s", err)
fmt.Printf("could not get current cdp info - %s %s \n", string(ownerAddress), string(collateralType))
return err return err
} }
@ -78,7 +77,7 @@ func QueryCdpsByDenomCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
Short: "Query cdps by collateral type", Short: "Query cdps by collateral type",
Long: strings.TrimSpace(`Query cdps by a specific collateral type, or query all cdps if none is specifed: Long: strings.TrimSpace(`Query cdps by a specific collateral type, or query all cdps if none is specifed:
$ <appcli> query cdp cdps atom $ <appcli> query cdp cdps uatom
`), `),
Args: cobra.MaximumNArgs(1), Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {

View File

@ -51,6 +51,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, gs GenesisState)
k.SetNextCdpID(ctx, gs.StartingCdpID) k.SetNextCdpID(ctx, gs.StartingCdpID)
k.SetDebtDenom(ctx, gs.DebtDenom) k.SetDebtDenom(ctx, gs.DebtDenom)
k.SetGovDenom(ctx, gs.GovDenom)
for _, d := range gs.Deposits { for _, d := range gs.Deposits {
k.SetDeposit(ctx, d) k.SetDeposit(ctx, d)

View File

@ -39,16 +39,20 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
cdpGenesis := cdp.GenesisState{ cdpGenesis := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
CollateralParams: cdp.CollateralParams{ CollateralParams: cdp.CollateralParams{
{ {
Denom: asset, Denom: asset,
LiquidationRatio: liquidationRatio, LiquidationRatio: liquidationRatio,
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
Prefix: 0x20, LiquidationPenalty: d("0.05"),
ConversionFactor: i(6), AuctionSize: i(1000000000),
MarketID: asset + ":usd", Prefix: 0x20,
ConversionFactor: i(6),
MarketID: asset + ":usd",
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParams: cdp.DebtParams{
@ -63,6 +67,7 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom, DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{}, CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousBlockTime: cdp.DefaultPreviousBlockTime,
} }
@ -97,25 +102,31 @@ func NewPricefeedGenStateMulti() app.GenesisState {
func NewCDPGenStateMulti() app.GenesisState { func NewCDPGenStateMulti() app.GenesisState {
cdpGenesis := cdp.GenesisState{ cdpGenesis := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)), GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
CollateralParams: cdp.CollateralParams{ CollateralParams: cdp.CollateralParams{
{ {
Denom: "xrp", Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("2.0"), LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)), DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
Prefix: 0x20, LiquidationPenalty: d("0.05"),
MarketID: "xrp:usd", AuctionSize: i(7000000000),
ConversionFactor: i(6), Prefix: 0x20,
MarketID: "xrp:usd",
ConversionFactor: i(6),
}, },
{ {
Denom: "btc", Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)), DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
Prefix: 0x21, LiquidationPenalty: d("0.025"),
MarketID: "btc:usd", AuctionSize: i(10000000),
ConversionFactor: i(8), Prefix: 0x21,
MarketID: "btc:usd",
ConversionFactor: i(8),
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParams: cdp.DebtParams{
@ -137,6 +148,7 @@ func NewCDPGenStateMulti() app.GenesisState {
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom, DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{}, CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousBlockTime: cdp.DefaultPreviousBlockTime,
} }
@ -193,6 +205,15 @@ func badGenStates() []badGenState {
g10 := baseGenState() g10 := baseGenState()
g10.PreviousBlockTime = time.Time{} g10.PreviousBlockTime = time.Time{}
g11 := baseGenState()
g11.Params.CollateralParams[0].AuctionSize = i(-10)
g12 := baseGenState()
g12.Params.CollateralParams[0].LiquidationPenalty = d("5.0")
g13 := baseGenState()
g13.GovDenom = ""
return []badGenState{ return []badGenState{
badGenState{Genesis: g1, Reason: "duplicate collateral denom"}, badGenState{Genesis: g1, Reason: "duplicate collateral denom"},
badGenState{Genesis: g2, Reason: "duplicate collateral prefix"}, badGenState{Genesis: g2, Reason: "duplicate collateral prefix"},
@ -204,13 +225,18 @@ func badGenStates() []badGenState {
badGenState{Genesis: g8, Reason: "debt param not found in global debt limit"}, badGenState{Genesis: g8, Reason: "debt param not found in global debt limit"},
badGenState{Genesis: g9, Reason: "debt denom not set"}, badGenState{Genesis: g9, Reason: "debt denom not set"},
badGenState{Genesis: g10, Reason: "previous block time 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"},
} }
} }
func baseGenState() cdp.GenesisState { func baseGenState() cdp.GenesisState {
return cdp.GenesisState{ return cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)), GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
CollateralParams: cdp.CollateralParams{ CollateralParams: cdp.CollateralParams{
{ {
Denom: "xrp", Denom: "xrp",
@ -250,6 +276,7 @@ func baseGenState() cdp.GenesisState {
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom, DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{}, CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousBlockTime: cdp.DefaultPreviousBlockTime,
} }

231
x/cdp/keeper/auctions.go Normal file
View File

@ -0,0 +1,231 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/cdp/types"
)
const (
// factor for setting the initial value of gov tokens to sell at debt auctions -- assuming stable token is ~1 usd, this starts the auction with a price of $0.01 KAVA
dump = 100
)
type partialDeposit struct {
Depositor sdk.AccAddress
Amount sdk.Coins
DebtShare sdk.Int
}
func newPartialDeposit(depositor sdk.AccAddress, amount sdk.Coins, ds sdk.Int) partialDeposit {
return partialDeposit{
Depositor: depositor,
Amount: amount,
DebtShare: ds,
}
}
type partialDeposits []partialDeposit
func (pd partialDeposits) SumCollateral() (sum sdk.Int) {
sum = sdk.ZeroInt()
for _, d := range pd {
sum = sum.Add(d.Amount[0].Amount)
}
return
}
func (pd partialDeposits) SumDebt() (sum sdk.Int) {
sum = sdk.ZeroInt()
for _, d := range pd {
sum = sum.Add(d.DebtShare)
}
return
}
// AuctionCollateral creates auctions from the input deposits which attempt to raise the corresponding amount of debt
func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) sdk.Error {
auctionSize := k.getAuctionSize(ctx, deposits[0].Amount[0].Denom)
partialAuctionDeposits := partialDeposits{}
totalCollateral := deposits.SumCollateral()
for totalCollateral.GT(sdk.ZeroInt()) {
for i, dep := range deposits {
if dep.Amount.IsZero() {
continue
}
collateralAmount := dep.Amount[0].Amount
collateralDenom := dep.Amount[0].Denom
// create auctions from individual deposits that are larger than the auction size
debtChange, collateralChange, err := k.CreateAuctionsFromDeposit(ctx, dep, debt, totalCollateral, auctionSize, bidDenom)
if err != nil {
return err
}
debt = debt.Sub(debtChange)
totalCollateral = totalCollateral.Sub(collateralChange)
dep.Amount = sdk.NewCoins(sdk.NewCoin(collateralDenom, collateralAmount.Sub(collateralChange)))
collateralAmount = collateralAmount.Sub(collateralChange)
// if there is leftover collateral that is less than a lot
if !dep.Amount.IsZero() {
// figure out how much debt this deposit accounts for
// (depositCollateral / totalCollateral) * totalDebtFromCDP
debtCoveredByDeposit := (collateralAmount.Quo(totalCollateral)).Mul(debt)
// if adding this deposit to the other partial deposits is less than a lot
if (partialAuctionDeposits.SumCollateral().Add(collateralAmount)).LT(auctionSize) {
// append the deposit to the partial deposits and zero out the deposit
pd := newPartialDeposit(dep.Depositor, dep.Amount, debtCoveredByDeposit)
partialAuctionDeposits = append(partialAuctionDeposits, pd)
dep.Amount = sdk.NewCoins(sdk.NewCoin(collateralDenom, sdk.ZeroInt()))
} else {
// if the sum of partial deposits now makes a lot
partialCollateral := sdk.NewCoins(sdk.NewCoin(collateralDenom, auctionSize.Sub(partialAuctionDeposits.SumCollateral())))
partialAmount := partialCollateral[0].Amount
partialDebt := (partialAmount.Quo(collateralAmount)).Mul(debtCoveredByDeposit)
// create a partial deposit from the deposit
partialDep := newPartialDeposit(dep.Depositor, partialCollateral, partialDebt)
// append it to the partial deposits
partialAuctionDeposits = append(partialAuctionDeposits, partialDep)
// create an auction from the partial deposits
debtChange, collateralChange, err := k.CreateAuctionFromPartialDeposits(ctx, partialAuctionDeposits, debt, totalCollateral, auctionSize, bidDenom)
if err != nil {
return err
}
debt = debt.Sub(debtChange)
totalCollateral = totalCollateral.Sub(collateralChange)
// reset partial deposits and update the deposit amount
partialAuctionDeposits = partialDeposits{}
dep.Amount = sdk.NewCoins(sdk.NewCoin(collateralDenom, collateralAmount.Sub(partialAmount)))
}
}
deposits[i] = dep
totalCollateral = deposits.SumCollateral()
}
}
if partialAuctionDeposits.SumCollateral().GT(sdk.ZeroInt()) {
_, _, err := k.CreateAuctionFromPartialDeposits(ctx, partialAuctionDeposits, debt, totalCollateral, partialAuctionDeposits.SumCollateral(), bidDenom)
if err != nil {
return err
}
}
return nil
}
// CreateAuctionsFromDeposit creates auctions from the input deposit until there is less than auctionSize left on the deposit
func (k Keeper) CreateAuctionsFromDeposit(ctx sdk.Context, dep types.Deposit, debt sdk.Int, totalCollateral sdk.Int, auctionSize sdk.Int, principalDenom string) (debtChange sdk.Int, collateralChange sdk.Int, err sdk.Error) {
debtChange = sdk.ZeroInt()
collateralChange = sdk.ZeroInt()
depositAmount := dep.Amount[0].Amount
depositDenom := dep.Amount[0].Denom
for depositAmount.GTE(auctionSize) {
// figure out how much debt is covered by one lots worth of collateral
depositDebtAmount := (sdk.NewDecFromInt(auctionSize).Quo(sdk.NewDecFromInt(totalCollateral))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
// start an auction for one lot, attempting to raise depositDebtAmount
_, err := k.auctionKeeper.StartCollateralAuction(
ctx, types.LiquidatorMacc, sdk.NewCoin(depositDenom, auctionSize), sdk.NewCoin(principalDenom, depositDebtAmount), []sdk.AccAddress{dep.Depositor},
[]sdk.Int{auctionSize}, sdk.NewCoin(k.GetDebtDenom(ctx), depositDebtAmount))
if err != nil {
return sdk.ZeroInt(), sdk.ZeroInt(), err
}
depositAmount = depositAmount.Sub(auctionSize)
totalCollateral = totalCollateral.Sub(auctionSize)
debt = debt.Sub(depositDebtAmount)
// subtract one lot's worth of debt from the total debt covered by this deposit
debtChange = debtChange.Add(depositDebtAmount)
collateralChange = collateralChange.Add(auctionSize)
}
return debtChange, collateralChange, nil
}
// CreateAuctionFromPartialDeposits creates an auction from the input partial deposits
func (k Keeper) CreateAuctionFromPartialDeposits(ctx sdk.Context, partialDeps partialDeposits, debt sdk.Int, collateral sdk.Int, auctionSize sdk.Int, bidDenom string) (debtChange, collateralChange sdk.Int, err sdk.Error) {
returnAddrs := []sdk.AccAddress{}
returnWeights := []sdk.Int{}
for _, pd := range partialDeps {
returnAddrs = append(returnAddrs, pd.Depositor)
returnWeights = append(returnWeights, pd.DebtShare)
}
_, err = k.auctionKeeper.StartCollateralAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(partialDeps[0].Amount[0].Denom, auctionSize), sdk.NewCoin(bidDenom, partialDeps.SumDebt()), returnAddrs, returnWeights, sdk.NewCoin(k.GetDebtDenom(ctx), partialDeps.SumDebt()))
if err != nil {
return sdk.ZeroInt(), sdk.ZeroInt(), err
}
debtChange = partialDeps.SumDebt()
collateralChange = partialDeps.SumCollateral()
return debtChange, collateralChange, nil
}
// NetSurplusAndDebt burns surplus and debt coins equal to the minimum of surplus and debt balances held by the liquidator module account
// for example, if there is 1000 debt and 100 surplus, 100 surplus and 100 debt are burned, netting to 900 debt
func (k Keeper) NetSurplusAndDebt(ctx sdk.Context) sdk.Error {
totalSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc)
debt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
netAmount := sdk.MinInt(totalSurplus, debt)
if netAmount.IsZero() {
return nil
}
err := k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), netAmount)))
if err != nil {
return err
}
for netAmount.GT(sdk.ZeroInt()) {
for _, dp := range k.GetParams(ctx).DebtParams {
balance := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
if balance.LT(netAmount) {
err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, balance)))
if err != nil {
return err
}
netAmount = netAmount.Sub(balance)
} else {
err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, netAmount)))
if err != nil {
return err
}
netAmount = sdk.ZeroInt()
}
}
}
return nil
}
// GetTotalSurplus returns the total amount of surplus tokens held by the liquidator module account
func (k Keeper) GetTotalSurplus(ctx sdk.Context, accountName string) sdk.Int {
acc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
totalSurplus := sdk.ZeroInt()
for _, dp := range k.GetParams(ctx).DebtParams {
surplus := acc.GetCoins().AmountOf(dp.Denom)
totalSurplus = totalSurplus.Add(surplus)
}
return totalSurplus
}
// GetTotalDebt returns the total amount of debt tokens held by the liquidator module account
func (k Keeper) GetTotalDebt(ctx sdk.Context, accountName string) sdk.Int {
acc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
debt := acc.GetCoins().AmountOf(k.GetDebtDenom(ctx))
return debt
}
// RunSurplusAndDebtAuctions nets the surplus and debt balances and then creates surplus or debt auctions if the remaining balance is above the auction threshold parameter
func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) sdk.Error {
k.NetSurplusAndDebt(ctx)
remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc)
params := k.GetParams(ctx)
if remainingDebt.GTE(params.DebtAuctionThreshold) {
_, err := k.auctionKeeper.StartDebtAuction(ctx, types.LiquidatorMacc, sdk.NewCoin("usdx", remainingDebt), sdk.NewCoin(k.GetGovDenom(ctx), remainingDebt.Mul(sdk.NewInt(dump))), sdk.NewCoin(k.GetDebtDenom(ctx), remainingDebt))
if err != nil {
return err
}
}
remainingSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc)
if remainingSurplus.GTE(params.SurplusAuctionThreshold) {
for _, dp := range params.DebtParams {
surplusLot := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(dp.Denom, surplusLot), k.GetGovDenom(ctx))
if err != nil {
return err
}
}
}
return nil
}

View File

@ -0,0 +1,76 @@
package keeper_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/auction"
"github.com/kava-labs/kava/x/cdp/keeper"
"github.com/kava-labs/kava/x/cdp/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
type AuctionTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
}
func (suite *AuctionTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
tApp.InitializeFromGenesisStates(
NewPricefeedGenStateMulti(),
NewCDPGenStateMulti(),
)
keeper := tApp.GetCDPKeeper()
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
return
}
func (suite *AuctionTestSuite) TestNetDebtSurplus() {
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("debt", 100)))
suite.NoError(err)
err = sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("usdx", 10)))
suite.NoError(err)
suite.NotPanics(func() { suite.keeper.NetSurplusAndDebt(suite.ctx) })
acc := sk.GetModuleAccount(suite.ctx, types.LiquidatorMacc)
suite.Equal(cs(c("debt", 90)), acc.GetCoins())
}
func (suite *AuctionTestSuite) TestSurplusAuction() {
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("usdx", 10000)))
suite.NoError(err)
err = sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("debt", 1000)))
suite.NoError(err)
suite.keeper.RunSurplusAndDebtAuctions(suite.ctx)
acc := sk.GetModuleAccount(suite.ctx, auction.ModuleName)
suite.Equal(cs(c("usdx", 9000)), acc.GetCoins())
}
func (suite *AuctionTestSuite) TestDebtAuction() {
sk := suite.app.GetSupplyKeeper()
err := sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("usdx", 1000)))
suite.NoError(err)
err = sk.MintCoins(suite.ctx, types.LiquidatorMacc, cs(c("debt", 10000)))
suite.NoError(err)
suite.keeper.RunSurplusAndDebtAuctions(suite.ctx)
acc := sk.GetModuleAccount(suite.ctx, auction.ModuleName)
suite.Equal(cs(c("debt", 9000)), acc.GetCoins())
}
func TestAuctionTestSuite(t *testing.T) {
suite.Run(t, new(AuctionTestSuite))
}

View File

@ -303,6 +303,14 @@ func (k Keeper) GetDebtDenom(ctx sdk.Context) (denom string) {
return return
} }
// GetGovDenom returns the denom of debt in the system
func (k Keeper) GetGovDenom(ctx sdk.Context) (denom string) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DebtDenomKey)
bz := store.Get([]byte{})
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &denom)
return
}
// SetDebtDenom set the denom of debt in the system // SetDebtDenom set the denom of debt in the system
func (k Keeper) SetDebtDenom(ctx sdk.Context, denom string) { func (k Keeper) SetDebtDenom(ctx sdk.Context, denom string) {
if denom == "" { if denom == "" {
@ -313,6 +321,16 @@ func (k Keeper) SetDebtDenom(ctx sdk.Context, denom string) {
return return
} }
// SetGovDenom set the denom of the governance token in the system
func (k Keeper) SetGovDenom(ctx sdk.Context, denom string) {
if denom == "" {
panic("gov denom not set in genesis")
}
store := prefix.NewStore(ctx.KVStore(k.key), types.GovDenomKey)
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(denom))
return
}
// ValidateCollateral validates that a collateral is valid for use in cdps // ValidateCollateral validates that a collateral is valid for use in cdps
func (k Keeper) ValidateCollateral(ctx sdk.Context, collateral sdk.Coins) sdk.Error { func (k Keeper) ValidateCollateral(ctx sdk.Context, collateral sdk.Coins) sdk.Error {
if len(collateral) != 1 { if len(collateral) != 1 {

View File

@ -18,13 +18,8 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, deposit
if !found { if !found {
return types.ErrCdpNotFound(k.codespace, owner, collateral[0].Denom) return types.ErrCdpNotFound(k.codespace, owner, collateral[0].Denom)
} }
// deposits blocked if cdp is in liquidation, have to check all deposits
err = k.ValidateAvailableCDP(ctx, cdp.ID)
if err != nil {
return err
}
deposit, found := k.GetDeposit(ctx, types.StatusNil, cdp.ID, depositor) deposit, found := k.GetDeposit(ctx, cdp.ID, depositor)
if found { if found {
deposit.Amount = deposit.Amount.Add(collateral) deposit.Amount = deposit.Amount.Add(collateral)
} else { } else {
@ -67,12 +62,7 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi
if !found { if !found {
return types.ErrCdpNotFound(k.codespace, owner, collateral[0].Denom) return types.ErrCdpNotFound(k.codespace, owner, collateral[0].Denom)
} }
// withdrawals blocked if cdp is in liquidation deposit, found := k.GetDeposit(ctx, cdp.ID, depositor)
err = k.ValidateAvailableCDP(ctx, cdp.ID)
if err != nil {
return err
}
deposit, found := k.GetDeposit(ctx, types.StatusNil, cdp.ID, depositor)
if !found { if !found {
return types.ErrDepositNotFound(k.codespace, depositor, cdp.ID) return types.ErrDepositNotFound(k.codespace, depositor, cdp.ID)
} }
@ -113,28 +103,17 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi
deposit.Amount = deposit.Amount.Sub(collateral) deposit.Amount = deposit.Amount.Sub(collateral)
if deposit.Amount.IsZero() { if deposit.Amount.IsZero() {
k.DeleteDeposit(ctx, types.StatusNil, deposit.CdpID, deposit.Depositor) k.DeleteDeposit(ctx, deposit.CdpID, deposit.Depositor)
} else { } else {
k.SetDeposit(ctx, deposit) k.SetDeposit(ctx, deposit)
} }
return nil return nil
} }
// ValidateAvailableCDP validates that the deposits of a cdp are not in liquidation
func (k Keeper) ValidateAvailableCDP(ctx sdk.Context, cdpID uint64) sdk.Error {
deposits := k.GetDeposits(ctx, cdpID)
for _, d := range deposits {
if d.InLiquidation {
return types.ErrCdpNotAvailable(k.codespace, cdpID)
}
}
return nil
}
// GetDeposit returns the deposit of a depositor on a particular cdp from the store // GetDeposit returns the deposit of a depositor on a particular cdp from the store
func (k Keeper) GetDeposit(ctx sdk.Context, status types.DepositStatus, cdpID uint64, depositor sdk.AccAddress) (deposit types.Deposit, found bool) { func (k Keeper) GetDeposit(ctx sdk.Context, cdpID uint64, depositor sdk.AccAddress) (deposit types.Deposit, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix)
bz := store.Get(types.DepositKey(status, cdpID, depositor)) bz := store.Get(types.DepositKey(cdpID, depositor))
if bz == nil { if bz == nil {
return deposit, false return deposit, false
} }
@ -147,35 +126,20 @@ func (k Keeper) GetDeposit(ctx sdk.Context, status types.DepositStatus, cdpID ui
func (k Keeper) SetDeposit(ctx sdk.Context, deposit types.Deposit) { func (k Keeper) SetDeposit(ctx sdk.Context, deposit types.Deposit) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(deposit) bz := k.cdc.MustMarshalBinaryLengthPrefixed(deposit)
if deposit.InLiquidation { store.Set(types.DepositKey(deposit.CdpID, deposit.Depositor), bz)
store.Set(types.DepositKey(types.StatusLiquidated, deposit.CdpID, deposit.Depositor), bz)
return
}
store.Set(types.DepositKey(types.StatusNil, deposit.CdpID, deposit.Depositor), bz)
} }
// DeleteDeposit deletes a deposit from the store // DeleteDeposit deletes a deposit from the store
func (k Keeper) DeleteDeposit(ctx sdk.Context, status types.DepositStatus, cdpID uint64, depositor sdk.AccAddress) { func (k Keeper) DeleteDeposit(ctx sdk.Context, cdpID uint64, depositor sdk.AccAddress) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix)
store.Delete(types.DepositKey(status, cdpID, depositor)) store.Delete(types.DepositKey(cdpID, depositor))
} }
// IterateDeposits iterates over the all the deposits of a cdp and performs a callback function // IterateDeposits iterates over the all the deposits of a cdp and performs a callback function
func (k Keeper) IterateDeposits(ctx sdk.Context, cdpID uint64, cb func(deposit types.Deposit) (stop bool)) { func (k Keeper) IterateDeposits(ctx sdk.Context, cdpID uint64, cb func(deposit types.Deposit) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, types.DepositIterKey(types.StatusNil, cdpID)) iterator := sdk.KVStorePrefixIterator(store, types.GetCdpIDBytes(cdpID))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var deposit types.Deposit
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit)
if cb(deposit) {
break
}
}
iterator = sdk.KVStorePrefixIterator(store, types.DepositIterKey(types.StatusLiquidated, cdpID))
defer iterator.Close() defer iterator.Close()
for ; iterator.Valid(); iterator.Next() { for ; iterator.Valid(); iterator.Next() {
@ -196,28 +160,3 @@ func (k Keeper) GetDeposits(ctx sdk.Context, cdpID uint64) (deposits types.Depos
}) })
return return
} }
// IterateLiquidatedDeposits iterates over the all liquidated deposits performs a callback function
func (k Keeper) IterateLiquidatedDeposits(ctx sdk.Context, cb func(deposit types.Deposit) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositKeyPrefix)
iterator := sdk.KVStorePrefixIterator(store, []byte{types.StatusLiquidated.AsByte()})
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var deposit types.Deposit
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &deposit)
if cb(deposit) {
break
}
}
}
// GetAllLiquidatedDeposits returns all deposits with status liquidated
func (k Keeper) GetAllLiquidatedDeposits(ctx sdk.Context) (deposits types.Deposits) {
k.IterateLiquidatedDeposits(ctx, func(deposit types.Deposit) bool {
deposits = append(deposits, deposit)
return false
})
return
}

View File

@ -45,15 +45,15 @@ func (suite *DepositTestSuite) SetupTest() {
} }
func (suite *DepositTestSuite) TestGetSetDeposit() { func (suite *DepositTestSuite) TestGetSetDeposit() {
d, found := suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0]) d, found := suite.keeper.GetDeposit(suite.ctx, uint64(1), suite.addrs[0])
suite.True(found) suite.True(found)
td := types.NewDeposit(uint64(1), suite.addrs[0], cs(c("xrp", 400000000))) td := types.NewDeposit(uint64(1), suite.addrs[0], cs(c("xrp", 400000000)))
suite.True(d.Equals(td)) suite.True(d.Equals(td))
ds := suite.keeper.GetDeposits(suite.ctx, uint64(1)) ds := suite.keeper.GetDeposits(suite.ctx, uint64(1))
suite.Equal(1, len(ds)) suite.Equal(1, len(ds))
suite.True(ds[0].Equals(td)) suite.True(ds[0].Equals(td))
suite.keeper.DeleteDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0]) suite.keeper.DeleteDeposit(suite.ctx, uint64(1), suite.addrs[0])
_, found = suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0]) _, found = suite.keeper.GetDeposit(suite.ctx, uint64(1), suite.addrs[0])
suite.False(found) suite.False(found)
ds = suite.keeper.GetDeposits(suite.ctx, uint64(1)) ds = suite.keeper.GetDeposits(suite.ctx, uint64(1))
suite.Equal(0, len(ds)) suite.Equal(0, len(ds))
@ -62,7 +62,7 @@ func (suite *DepositTestSuite) TestGetSetDeposit() {
func (suite *DepositTestSuite) TestDepositCollateral() { func (suite *DepositTestSuite) TestDepositCollateral() {
err := suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 10000000))) err := suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 10000000)))
suite.NoError(err) suite.NoError(err)
d, found := suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0]) d, found := suite.keeper.GetDeposit(suite.ctx, uint64(1), suite.addrs[0])
suite.True(found) suite.True(found)
td := types.NewDeposit(uint64(1), suite.addrs[0], cs(c("xrp", 410000000))) td := types.NewDeposit(uint64(1), suite.addrs[0], cs(c("xrp", 410000000)))
suite.True(d.Equals(td)) suite.True(d.Equals(td))
@ -83,7 +83,7 @@ func (suite *DepositTestSuite) TestDepositCollateral() {
err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[1], cs(c("xrp", 10000000))) err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[1], cs(c("xrp", 10000000)))
suite.NoError(err) suite.NoError(err)
d, found = suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[1]) d, found = suite.keeper.GetDeposit(suite.ctx, uint64(1), suite.addrs[1])
suite.True(found) suite.True(found)
td = types.NewDeposit(uint64(1), suite.addrs[1], cs(c("xrp", 10000000))) td = types.NewDeposit(uint64(1), suite.addrs[1], cs(c("xrp", 10000000)))
suite.True(d.Equals(td)) suite.True(d.Equals(td))
@ -98,24 +98,6 @@ func (suite *DepositTestSuite) TestWithdrawCollateral() {
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], cs(c("xrp", 10000000))) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], cs(c("xrp", 10000000)))
suite.Equal(types.CodeCdpNotFound, err.Result().Code) suite.Equal(types.CodeCdpNotFound, err.Result().Code)
d, _ := suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0])
d.InLiquidation = true
suite.keeper.DeleteDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0])
suite.keeper.SetDeposit(suite.ctx, d)
_, f := suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0])
suite.False(f)
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 10000000)))
suite.Equal(types.CodeCdpNotAvailable, err.Result().Code)
d, f = suite.keeper.GetDeposit(suite.ctx, types.StatusLiquidated, uint64(1), suite.addrs[0])
suite.True(f)
suite.keeper.DeleteDeposit(suite.ctx, types.StatusLiquidated, uint64(1), suite.addrs[0])
d.InLiquidation = false
suite.keeper.SetDeposit(suite.ctx, d)
_, f = suite.keeper.GetDeposit(suite.ctx, types.StatusLiquidated, uint64(1), suite.addrs[0])
suite.False(f)
cd, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) cd, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
cd.AccumulatedFees = cs(c("usdx", 1)) cd.AccumulatedFees = cs(c("usdx", 1))
suite.keeper.SetCDP(suite.ctx, cd) suite.keeper.SetCDP(suite.ctx, cd)
@ -124,9 +106,9 @@ func (suite *DepositTestSuite) TestWithdrawCollateral() {
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 10000000))) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 10000000)))
suite.NoError(err) suite.NoError(err)
d, _ = suite.keeper.GetDeposit(suite.ctx, types.StatusNil, uint64(1), suite.addrs[0]) dep, _ := suite.keeper.GetDeposit(suite.ctx, uint64(1), suite.addrs[0])
td := types.NewDeposit(uint64(1), suite.addrs[0], cs(c("xrp", 390000000))) td := types.NewDeposit(uint64(1), suite.addrs[0], cs(c("xrp", 390000000)))
suite.True(d.Equals(td)) suite.True(dep.Equals(td))
ak := suite.app.GetAccountKeeper() ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[0]) acc := ak.GetAccount(suite.ctx, suite.addrs[0])
suite.Equal(i(110000000), acc.GetCoins().AmountOf("xrp")) suite.Equal(i(110000000), acc.GetCoins().AmountOf("xrp"))
@ -135,20 +117,6 @@ func (suite *DepositTestSuite) TestWithdrawCollateral() {
suite.Equal(types.CodeDepositNotFound, err.Result().Code) suite.Equal(types.CodeDepositNotFound, err.Result().Code)
} }
func (suite *DepositTestSuite) TestIterateLiquidatedDeposits() {
for j := 0; j < 10; j++ {
d := types.NewDeposit(uint64(j+2), suite.addrs[j], cs(c("xrp", 1000000)))
if j%2 == 0 {
d.InLiquidation = true
}
suite.keeper.SetDeposit(suite.ctx, d)
}
ds := suite.keeper.GetAllLiquidatedDeposits(suite.ctx)
for _, d := range ds {
suite.True(d.InLiquidation)
}
suite.Equal(5, len(ds))
}
func TestDepositTestSuite(t *testing.T) { func TestDepositTestSuite(t *testing.T) {
suite.Run(t, new(DepositTestSuite)) suite.Run(t, new(DepositTestSuite))
} }

View File

@ -14,11 +14,7 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string
if !found { if !found {
return types.ErrCdpNotFound(k.codespace, owner, denom) return types.ErrCdpNotFound(k.codespace, owner, denom)
} }
err := k.ValidateAvailableCDP(ctx, cdp.ID) err := k.ValidatePrincipalDraw(ctx, principal)
if err != nil {
return err
}
err = k.ValidatePrincipalDraw(ctx, principal)
if err != nil { if err != nil {
return err return err
} }
@ -84,11 +80,7 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri
if !found { if !found {
return types.ErrCdpNotFound(k.codespace, owner, denom) return types.ErrCdpNotFound(k.codespace, owner, denom)
} }
err := k.ValidateAvailableCDP(ctx, cdp.ID) err := k.ValidatePaymentCoins(ctx, cdp, payment)
if err != nil {
return err
}
err = k.ValidatePaymentCoins(ctx, cdp, payment)
if err != nil { if err != nil {
return err return err
} }
@ -189,7 +181,7 @@ func (k Keeper) ReturnCollateral(ctx sdk.Context, cdp types.CDP) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
k.DeleteDeposit(ctx, types.StatusNil, cdp.ID, deposit.Depositor) k.DeleteDeposit(ctx, cdp.ID, deposit.Depositor)
} }
} }

View File

@ -39,16 +39,20 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
cdpGenesis := cdp.GenesisState{ cdpGenesis := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
CollateralParams: cdp.CollateralParams{ CollateralParams: cdp.CollateralParams{
{ {
Denom: asset, Denom: asset,
LiquidationRatio: liquidationRatio, LiquidationRatio: liquidationRatio,
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
Prefix: 0x20, LiquidationPenalty: d("0.05"),
ConversionFactor: i(6), AuctionSize: i(100),
MarketID: asset + ":usd", Prefix: 0x20,
ConversionFactor: i(6),
MarketID: asset + ":usd",
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParams: cdp.DebtParams{
@ -63,6 +67,7 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom, DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{}, CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousBlockTime: cdp.DefaultPreviousBlockTime,
} }
@ -97,25 +102,31 @@ func NewPricefeedGenStateMulti() app.GenesisState {
func NewCDPGenStateMulti() app.GenesisState { func NewCDPGenStateMulti() app.GenesisState {
cdpGenesis := cdp.GenesisState{ cdpGenesis := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)), GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
CollateralParams: cdp.CollateralParams{ CollateralParams: cdp.CollateralParams{
{ {
Denom: "xrp", Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("2.0"), LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)), DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
Prefix: 0x20, LiquidationPenalty: d("0.05"),
MarketID: "xrp:usd", AuctionSize: i(7000000000),
ConversionFactor: i(6), Prefix: 0x20,
MarketID: "xrp:usd",
ConversionFactor: i(6),
}, },
{ {
Denom: "btc", Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)), DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
Prefix: 0x21, LiquidationPenalty: d("0.025"),
MarketID: "btc:usd", AuctionSize: i(10000000),
ConversionFactor: i(8), Prefix: 0x21,
MarketID: "btc:usd",
ConversionFactor: i(8),
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParams: cdp.DebtParams{
@ -137,6 +148,7 @@ func NewCDPGenStateMulti() app.GenesisState {
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom, DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{}, CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousBlockTime: cdp.DefaultPreviousBlockTime,
} }

View File

@ -17,22 +17,29 @@ type Keeper struct {
paramSubspace subspace.Subspace paramSubspace subspace.Subspace
pricefeedKeeper types.PricefeedKeeper pricefeedKeeper types.PricefeedKeeper
supplyKeeper types.SupplyKeeper supplyKeeper types.SupplyKeeper
auctionKeeper types.AuctionKeeper
codespace sdk.CodespaceType codespace sdk.CodespaceType
} }
// NewKeeper creates a new keeper // NewKeeper creates a new keeper
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType) Keeper { func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, ak types.AuctionKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType) Keeper {
// ensure module account is set // ensure cdp module account is set
if addr := sk.GetModuleAddress(types.ModuleName); addr == nil { if addr := sk.GetModuleAddress(types.ModuleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
} }
// ensure liquidator module account is set
if addr := sk.GetModuleAddress(types.LiquidatorMacc); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.LiquidatorMacc))
}
return Keeper{ return Keeper{
key: key, key: key,
cdc: cdc, cdc: cdc,
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
pricefeedKeeper: pfk, pricefeedKeeper: pfk,
auctionKeeper: ak,
supplyKeeper: sk, supplyKeeper: sk,
codespace: codespace, codespace: codespace,
} }

View File

@ -52,8 +52,7 @@ func (k Keeper) GetDenomPrefix(ctx sdk.Context, denom string) (byte, bool) {
return 0x00, false return 0x00, false
} }
// private methods panic if the input is invalid // private methods assume collateral has been validated, panic if the input is invalid
func (k Keeper) getDenomFromByte(ctx sdk.Context, db byte) string { func (k Keeper) getDenomFromByte(ctx sdk.Context, db byte) string {
params := k.GetParams(ctx) params := k.GetParams(ctx)
for _, cp := range params.CollateralParams { for _, cp := range params.CollateralParams {
@ -79,3 +78,19 @@ func (k Keeper) getLiquidationRatio(ctx sdk.Context, denom string) sdk.Dec {
} }
return cp.LiquidationRatio return cp.LiquidationRatio
} }
func (k Keeper) getLiquidationPenalty(ctx sdk.Context, denom string) sdk.Dec {
cp, found := k.GetCollateral(ctx, denom)
if !found {
panic(fmt.Sprintf("collateral not found: %s", denom))
}
return cp.LiquidationPenalty
}
func (k Keeper) getAuctionSize(ctx sdk.Context, denom string) sdk.Int {
cp, found := k.GetCollateral(ctx, denom)
if !found {
panic(fmt.Sprintf("collateral not found: %s", denom))
}
return cp.AuctionSize
}

View File

@ -15,50 +15,51 @@ import (
// 4. decrements the total amount of principal outstanding for that collateral type // 4. decrements the total amount of principal outstanding for that collateral type
// (this is the equivalent of saying that fees are no longer accumulated by a cdp once it // (this is the equivalent of saying that fees are no longer accumulated by a cdp once it
// gets liquidated) // gets liquidated)
func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) { func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) sdk.Error {
// Calculate the previous collateral ratio
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
// Update fees // Update fees
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) 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) fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom)
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees) cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees)
cdp.FeesUpdated = ctx.BlockTime() cdp.FeesUpdated = ctx.BlockTime()
// Liquidate deposits // TODO implement liquidation penalty
// Move debt coins from cdp to liquidator account
deposits := k.GetDeposits(ctx, cdp.ID) deposits := k.GetDeposits(ctx, cdp.ID)
for _, dep := range deposits { debt := sdk.ZeroInt()
if !dep.InLiquidation { for _, pc := range cdp.Principal {
dep.InLiquidation = true debt = debt.Add(pc.Amount)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCdpLiquidation,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)),
),
)
k.DeleteDeposit(ctx, types.StatusNil, cdp.ID, dep.Depositor)
k.SetDeposit(ctx, dep)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, dep.Amount)
if err != nil {
panic(err)
}
} else {
return
}
}
// Transfer debt coins from cdp module account to liquidator module account
debtAmt := sdk.ZeroInt()
for _, dc := range cdp.Principal {
debtAmt = debtAmt.Add(dc.Amount)
} }
for _, dc := range cdp.AccumulatedFees { for _, dc := range cdp.AccumulatedFees {
debtAmt = debtAmt.Add(dc.Amount) debt = debt.Add(dc.Amount)
} }
debtCoins := sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), debtAmt)) debtCoin := sdk.NewCoin(k.GetDebtDenom(ctx), debt)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, debtCoins) err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(debtCoin))
if err != nil { if err != nil {
panic(err) return err
}
// liquidate deposits and send collateral from cdp to liquidator
for _, dep := range deposits {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCdpLiquidation,
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)),
),
)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, dep.Amount)
if err != nil {
return err
}
k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor)
}
err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal[0].Denom)
if err != nil {
return err
} }
// Decrement total principal for this collateral type // Decrement total principal for this collateral type
@ -71,6 +72,10 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) {
} }
k.DecrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, coinsToDecrement) k.DecrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, coinsToDecrement)
} }
k.RemoveCdpOwnerIndex(ctx, cdp)
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio)
k.DeleteCDP(ctx, cdp)
return nil
} }
// HandleNewDebt compounds the accumulated fees for the input collateral and principal coins. // HandleNewDebt compounds the accumulated fees for the input collateral and principal coins.
@ -88,15 +93,18 @@ func (k Keeper) HandleNewDebt(ctx sdk.Context, collateralDenom string, principal
} }
// LiquidateCdps seizes collateral from all CDPs below the input liquidation ratio // 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) { func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, liquidationRatio sdk.Dec) sdk.Error {
price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID)
if err != nil { if err != nil {
return return err
} }
normalizedRatio := sdk.OneDec().Quo(price.Price.Quo(liquidationRatio)) normalizedRatio := sdk.OneDec().Quo(price.Price.Quo(liquidationRatio))
cdpsToLiquidate := k.GetAllCdpsByDenomAndRatio(ctx, denom, normalizedRatio) cdpsToLiquidate := k.GetAllCdpsByDenomAndRatio(ctx, denom, normalizedRatio)
for _, c := range cdpsToLiquidate { for _, c := range cdpsToLiquidate {
k.SeizeCollateral(ctx, c) err := k.SeizeCollateral(ctx, c)
if err != nil {
return err
}
} }
return return nil
} }

View File

@ -8,6 +8,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation" "github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/auction"
"github.com/kava-labs/kava/x/cdp/keeper" "github.com/kava-labs/kava/x/cdp/keeper"
"github.com/kava-labs/kava/x/cdp/types" "github.com/kava-labs/kava/x/cdp/types"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
@ -55,15 +56,15 @@ func (suite *SeizeTestSuite) SetupTest() {
suite.ctx = ctx suite.ctx = ctx
suite.app = tApp suite.app = tApp
suite.keeper = tApp.GetCDPKeeper() suite.keeper = tApp.GetCDPKeeper()
randSource := rand.New(rand.NewSource(int64(777)))
for j := 0; j < 100; j++ { for j := 0; j < 100; j++ {
collateral := "xrp" collateral := "xrp"
amount := 10000000000 amount := 10000000000
debt := simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 750000000, 1249000000) debt := simulation.RandIntBetween(randSource, 750000000, 1249000000)
if j%2 == 0 { if j%2 == 0 {
collateral = "btc" collateral = "btc"
amount = 100000000 amount = 100000000
debt = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 2700000000, 5332000000) debt = simulation.RandIntBetween(randSource, 2700000000, 5332000000)
if debt >= 4000000000 { if debt >= 4000000000 {
tracker.btc = append(tracker.btc, uint64(j+1)) tracker.btc = append(tracker.btc, uint64(j+1))
tracker.debt += int64(debt) tracker.debt += int64(debt)
@ -103,16 +104,41 @@ func (suite *SeizeTestSuite) TestSeizeCollateral() {
p := cdp.Principal[0].Amount p := cdp.Principal[0].Amount
cl := cdp.Collateral[0].Amount cl := cdp.Collateral[0].Amount
tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
suite.keeper.SeizeCollateral(suite.ctx, cdp) err := suite.keeper.SeizeCollateral(suite.ctx, cdp)
suite.NoError(err)
tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
suite.Equal(tpb.Sub(tpa), p) suite.Equal(tpb.Sub(tpa), p)
liqModAcc := sk.GetModuleAccount(suite.ctx, types.LiquidatorMacc) auctionMacc := sk.GetModuleAccount(suite.ctx, auction.ModuleName)
suite.Equal(cs(c("debt", p.Int64()), c("xrp", cl.Int64())), liqModAcc.GetCoins()) suite.Equal(cs(c("debt", p.Int64()), c("xrp", cl.Int64())), auctionMacc.GetCoins())
ak := suite.app.GetAccountKeeper() ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[1]) acc := ak.GetAccount(suite.ctx, suite.addrs[1])
suite.Equal(p.Int64(), acc.GetCoins().AmountOf("usdx").Int64()) suite.Equal(p.Int64(), acc.GetCoins().AmountOf("usdx").Int64())
err := suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[1], cs(c("xrp", 10))) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[1], cs(c("xrp", 10)))
suite.Equal(types.CodeCdpNotAvailable, err.Result().Code) suite.Equal(types.CodeCdpNotFound, err.Result().Code)
}
func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() {
sk := suite.app.GetSupplyKeeper()
cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
err := suite.keeper.DepositCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], cs(c("xrp", 6999000000)))
suite.NoError(err)
cdp, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
deposits := suite.keeper.GetDeposits(suite.ctx, cdp.ID)
suite.Equal(2, len(deposits))
p := cdp.Principal[0].Amount
cl := cdp.Collateral[0].Amount
tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
suite.NoError(err)
tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
suite.Equal(tpb.Sub(tpa), p)
auctionMacc := sk.GetModuleAccount(suite.ctx, auction.ModuleName)
suite.Equal(cs(c("debt", p.Int64()), c("xrp", cl.Int64())), auctionMacc.GetCoins())
ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[1])
suite.Equal(p.Int64(), acc.GetCoins().AmountOf("usdx").Int64())
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[1], cs(c("xrp", 10)))
suite.Equal(types.CodeCdpNotFound, err.Result().Code)
} }
func (suite *SeizeTestSuite) TestLiquidateCdps() { func (suite *SeizeTestSuite) TestLiquidateCdps() {

View File

@ -8,50 +8,22 @@ import (
// Deposit defines an amount of coins deposited by an account to a cdp // Deposit defines an amount of coins deposited by an account to a cdp
type Deposit struct { type Deposit struct {
CdpID uint64 `json:"cdp_id" yaml:"cdp_id"` // cdpID of the cdp CdpID uint64 `json:"cdp_id" yaml:"cdp_id"` // cdpID of the cdp
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` // Address of the depositor
Amount sdk.Coins `json:"amount" yaml:"amount"` // Deposit amount Amount sdk.Coins `json:"amount" yaml:"amount"` // Deposit amount
InLiquidation bool `json:"in_liquidation" yaml:"in_liquidation"`
}
// DepositStatus is a type alias that represents a deposit status as a byte
type DepositStatus byte
// Valid Deposit statuses
const (
StatusNil DepositStatus = 0x00
StatusLiquidated DepositStatus = 0x01
)
// AsByte returns the status as byte
func (ds DepositStatus) AsByte() byte {
return byte(ds)
}
// StatusFromByte returns the status from its byte representation
func StatusFromByte(b byte) DepositStatus {
switch b {
case 0x00:
return StatusNil
case 0x01:
return StatusLiquidated
default:
panic(fmt.Sprintf("unrecognized deposit status, %v", b))
}
} }
// NewDeposit creates a new Deposit object // NewDeposit creates a new Deposit object
func NewDeposit(cdpID uint64, depositor sdk.AccAddress, amount sdk.Coins) Deposit { func NewDeposit(cdpID uint64, depositor sdk.AccAddress, amount sdk.Coins) Deposit {
return Deposit{cdpID, depositor, amount, false} return Deposit{cdpID, depositor, amount}
} }
// String implements fmt.Stringer // String implements fmt.Stringer
func (d Deposit) String() string { func (d Deposit) String() string {
return fmt.Sprintf(`Deposit for CDP %d: return fmt.Sprintf(`Deposit for CDP %d:
Depositor: %s Depositor: %s
Amount: %s Amount: %s`,
In Liquidation: %t`, d.CdpID, d.Depositor, d.Amount)
d.CdpID, d.Depositor, d.Amount, d.InLiquidation)
} }
// Deposits a collection of Deposit objects // Deposits a collection of Deposit objects
@ -65,9 +37,6 @@ func (ds Deposits) String() string {
out := fmt.Sprintf("Deposits for CDP %d:", ds[0].CdpID) out := fmt.Sprintf("Deposits for CDP %d:", ds[0].CdpID)
for _, dep := range ds { for _, dep := range ds {
out += fmt.Sprintf("\n %s: %s", dep.Depositor, dep.Amount) out += fmt.Sprintf("\n %s: %s", dep.Depositor, dep.Amount)
if dep.InLiquidation {
out += fmt.Sprintf("(in liquidation)")
}
} }
return out return out
} }
@ -81,3 +50,13 @@ func (d Deposit) Equals(comp Deposit) bool {
func (d Deposit) Empty() bool { func (d Deposit) Empty() bool {
return d.Equals(Deposit{}) return d.Equals(Deposit{})
} }
func (ds Deposits) SumCollateral() (sum sdk.Int) {
sum = sdk.ZeroInt()
for _, d := range ds {
if !d.Amount.IsZero() {
sum = sum.Add(d.Amount[0].Amount)
}
}
return
}

View File

@ -2,15 +2,17 @@ package types
// Event types for cdp module // Event types for cdp module
const ( const (
EventTypeCreateCdp = "create_cdp" EventTypeCreateCdp = "create_cdp"
EventTypeCdpDeposit = "cdp_deposit" EventTypeCdpDeposit = "cdp_deposit"
EventTypeCdpDraw = "cdp_draw" EventTypeCdpDraw = "cdp_draw"
EventTypeCdpRepay = "cdp_repayment" EventTypeCdpRepay = "cdp_repayment"
EventTypeCdpClose = "cdp_close" EventTypeCdpClose = "cdp_close"
EventTypeCdpWithdrawal = "cdp_withdrawal" EventTypeCdpWithdrawal = "cdp_withdrawal"
EventTypeCdpLiquidation = "cdp_liquidation" EventTypeCdpLiquidation = "cdp_liquidation"
EventTypeBeginBlockerFatal = "cdp_begin_block_error"
AttributeKeyCdpID = "cdp_id" AttributeKeyCdpID = "cdp_id"
AttributeKeyDepositor = "depositor" AttributeKeyDepositor = "depositor"
AttributeValueCategory = "cdp" AttributeValueCategory = "cdp"
AttributeKeyError = "error_message"
) )

View File

@ -32,3 +32,10 @@ type PricefeedKeeper interface {
SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, sdk.Error) SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, sdk.Error)
SetCurrentPrices(sdk.Context, string) sdk.Error SetCurrentPrices(sdk.Context, string) sdk.Error
} }
// AuctionKeeper expected interface for the auction keeper (noalias)
type AuctionKeeper interface {
StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error)
StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, sdk.Error)
StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, sdk.Error)
}

View File

@ -13,6 +13,7 @@ type GenesisState struct {
Deposits Deposits `json:"deposits" yaml:"deposits"` Deposits Deposits `json:"deposits" yaml:"deposits"`
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"` StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
DebtDenom string `json:"debt_denom" yaml:"debt_denom"` 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"` PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
} }
@ -24,6 +25,7 @@ func DefaultGenesisState() GenesisState {
Deposits: Deposits{}, Deposits: Deposits{},
StartingCdpID: DefaultCdpStartingID, StartingCdpID: DefaultCdpStartingID,
DebtDenom: DefaultDebtDenom, DebtDenom: DefaultDebtDenom,
GovDenom: DefaultGovDenom,
PreviousBlockTime: DefaultPreviousBlockTime, PreviousBlockTime: DefaultPreviousBlockTime,
} }
} }

View File

@ -51,10 +51,11 @@ var (
CollateralRatioIndexPrefix = []byte{0x02} CollateralRatioIndexPrefix = []byte{0x02}
CdpIDKey = []byte{0x03} CdpIDKey = []byte{0x03}
DebtDenomKey = []byte{0x04} DebtDenomKey = []byte{0x04}
DepositKeyPrefix = []byte{0x05} GovDenomKey = []byte{0x05}
PrincipalKeyPrefix = []byte{0x06} DepositKeyPrefix = []byte{0x06}
AccumulatorKeyPrefix = []byte{0x07} PrincipalKeyPrefix = []byte{0x07}
PreviousBlockTimeKey = []byte{0x08} AccumulatorKeyPrefix = []byte{0x08}
PreviousBlockTimeKey = []byte{0x09}
) )
var lenPositiveDec = len(SortableDecBytes(sdk.OneDec())) var lenPositiveDec = len(SortableDecBytes(sdk.OneDec()))
@ -95,28 +96,25 @@ func SplitDenomIterKey(key []byte) byte {
} }
// DepositKey key of a specific deposit in the store // DepositKey key of a specific deposit in the store
func DepositKey(status DepositStatus, cdpID uint64, depositor sdk.AccAddress) []byte { func DepositKey(cdpID uint64, depositor sdk.AccAddress) []byte {
return createKey([]byte{status.AsByte()}, sep, GetCdpIDBytes(cdpID), sep, depositor) return createKey(GetCdpIDBytes(cdpID), sep, depositor)
} }
// SplitDepositKey returns the component parts of a deposit key // SplitDepositKey returns the component parts of a deposit key
func SplitDepositKey(key []byte) (DepositStatus, uint64, sdk.AccAddress) { func SplitDepositKey(key []byte) (uint64, sdk.AccAddress) {
status := StatusFromByte(key[0]) cdpID := GetCdpIDFromBytes(key[0:8])
cdpID := GetCdpIDFromBytes(key[2:10]) addr := key[9:]
addr := key[11:] return cdpID, addr
return status, cdpID, addr
} }
// DepositIterKey returns the prefix key for iterating over deposits to a cdp // DepositIterKey returns the prefix key for iterating over deposits to a cdp
func DepositIterKey(status DepositStatus, cdpID uint64) []byte { func DepositIterKey(cdpID uint64) []byte {
return createKey([]byte{status.AsByte()}, sep, GetCdpIDBytes(cdpID)) return GetCdpIDBytes(cdpID)
} }
// SplitDepositIterKey returns the component parts of a key for iterating over deposits on a cdp // SplitDepositIterKey returns the component parts of a key for iterating over deposits on a cdp
func SplitDepositIterKey(key []byte) (status DepositStatus, cdpID uint64) { func SplitDepositIterKey(key []byte) (cdpID uint64) {
status = StatusFromByte(key[0]) return GetCdpIDFromBytes(key)
cdpID = GetCdpIDFromBytes(key[2:])
return status, cdpID
} }
// CollateralRatioBytes returns the liquidation ratio as sortable bytes // CollateralRatioBytes returns the liquidation ratio as sortable bytes

View File

@ -20,18 +20,16 @@ func TestKeys(t *testing.T) {
db = SplitDenomIterKey(denomKey) db = SplitDenomIterKey(denomKey)
require.Equal(t, byte(0x01), db) require.Equal(t, byte(0x01), db)
depositKey := DepositKey(StatusNil, 2, addr) depositKey := DepositKey(2, addr)
status, id, a := SplitDepositKey(depositKey) id, a := SplitDepositKey(depositKey)
require.Equal(t, 2, int(id)) require.Equal(t, 2, int(id))
require.Equal(t, a, addr) require.Equal(t, a, addr)
require.Equal(t, StatusNil, status)
depositIterKey := DepositIterKey(StatusLiquidated, 2) depositIterKey := DepositIterKey(2)
status, id = SplitDepositIterKey(depositIterKey) id = SplitDepositIterKey(depositIterKey)
require.Equal(t, 2, int(id)) require.Equal(t, 2, int(id))
require.Equal(t, StatusLiquidated, status)
require.Panics(t, func() { SplitDepositIterKey(append([]byte{0x03}, GetCdpIDBytes(2)...)) }) require.Panics(t, func() { SplitDepositIterKey([]byte{0x03}) })
collateralKey := CollateralRatioKey(0x01, 2, sdk.MustNewDecFromStr("1.50")) collateralKey := CollateralRatioKey(0x01, 2, sdk.MustNewDecFromStr("1.50"))
db, id, ratio := SplitCollateralRatioKey(collateralKey) db, id, ratio := SplitCollateralRatioKey(collateralKey)

View File

@ -15,12 +15,17 @@ var (
KeyCollateralParams = []byte("CollateralParams") KeyCollateralParams = []byte("CollateralParams")
KeyDebtParams = []byte("DebtParams") KeyDebtParams = []byte("DebtParams")
KeyCircuitBreaker = []byte("CircuitBreaker") KeyCircuitBreaker = []byte("CircuitBreaker")
KeyDebtThreshold = []byte("DebtThreshold")
KeySurplusThreshold = []byte("SurplusThreshold")
DefaultGlobalDebt = sdk.Coins{} DefaultGlobalDebt = sdk.Coins{}
DefaultCircuitBreaker = false DefaultCircuitBreaker = false
DefaultCollateralParams = CollateralParams{} DefaultCollateralParams = CollateralParams{}
DefaultDebtParams = DebtParams{} DefaultDebtParams = DebtParams{}
DefaultCdpStartingID = uint64(1) DefaultCdpStartingID = uint64(1)
DefaultDebtDenom = "debt" DefaultDebtDenom = "debt"
DefaultGovDenom = "ukava"
DefaultSurplusThreshold = sdk.NewInt(1000)
DefaultDebtThreshold = sdk.NewInt(1000)
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
minCollateralPrefix = 0 minCollateralPrefix = 0
maxCollateralPrefix = 255 maxCollateralPrefix = 255
@ -28,10 +33,12 @@ var (
// Params governance parameters for cdp module // Params governance parameters for cdp module
type Params struct { type Params struct {
CollateralParams CollateralParams `json:"collateral_params" yaml:"collateral_params"` CollateralParams CollateralParams `json:"collateral_params" yaml:"collateral_params"`
DebtParams DebtParams `json:"debt_params" yaml:"debt_params"` DebtParams DebtParams `json:"debt_params" yaml:"debt_params"`
GlobalDebtLimit sdk.Coins `json:"global_debt_limit" yaml:"global_debt_limit"` GlobalDebtLimit sdk.Coins `json:"global_debt_limit" yaml:"global_debt_limit"`
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"` SurplusAuctionThreshold sdk.Int `json:"surplus_auction_threshold" yaml:"surplus_auction_threshold"`
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
} }
// String implements fmt.Stringer // String implements fmt.Stringer
@ -40,35 +47,41 @@ func (p Params) String() string {
Global Debt Limit: %s Global Debt Limit: %s
Collateral Params: %s Collateral Params: %s
Debt Params: %s Debt Params: %s
Surplus Auction Threshold: %s
Debt Auction Threshold: %s
Circuit Breaker: %t`, Circuit Breaker: %t`,
p.GlobalDebtLimit, p.CollateralParams, p.DebtParams, p.CircuitBreaker, p.GlobalDebtLimit, p.CollateralParams, p.DebtParams, p.SurplusAuctionThreshold, p.DebtAuctionThreshold, p.CircuitBreaker,
) )
} }
// NewParams returns a new params object // NewParams returns a new params object
func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParams DebtParams, breaker bool) Params { func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParams DebtParams, surplusThreshold sdk.Int, debtThreshold sdk.Int, breaker bool) Params {
return Params{ return Params{
GlobalDebtLimit: debtLimit, GlobalDebtLimit: debtLimit,
CollateralParams: collateralParams, CollateralParams: collateralParams,
DebtParams: debtParams, DebtParams: debtParams,
CircuitBreaker: breaker, DebtAuctionThreshold: debtThreshold,
SurplusAuctionThreshold: surplusThreshold,
CircuitBreaker: breaker,
} }
} }
// DefaultParams returns default params for cdp module // DefaultParams returns default params for cdp module
func DefaultParams() Params { func DefaultParams() Params {
return NewParams(DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParams, DefaultCircuitBreaker) return NewParams(DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParams, DefaultSurplusThreshold, DefaultDebtThreshold, DefaultCircuitBreaker)
} }
// CollateralParam governance parameters for each collateral type within the cdp module // CollateralParam governance parameters for each collateral type within the cdp module
type CollateralParam struct { type CollateralParam struct {
Denom string `json:"denom" yaml:"denom"` // Coin name of collateral type Denom string `json:"denom" yaml:"denom"` // Coin name of collateral type
LiquidationRatio sdk.Dec `json:"liquidation_ratio" yaml:"liquidation_ratio"` // The ratio (Collateral (priced in stable coin) / Debt) under which a CDP will be liquidated LiquidationRatio sdk.Dec `json:"liquidation_ratio" yaml:"liquidation_ratio"` // The ratio (Collateral (priced in stable coin) / Debt) under which a CDP will be liquidated
DebtLimit sdk.Coins `json:"debt_limit" yaml:"debt_limit"` // Maximum amount of debt allowed to be drawn from this collateral type DebtLimit sdk.Coins `json:"debt_limit" yaml:"debt_limit"` // Maximum amount of debt allowed to be drawn from this collateral type
StabilityFee sdk.Dec `json:"stability_fee" yaml:"stability_fee"` // per second stability fee for loans opened using this collateral StabilityFee sdk.Dec `json:"stability_fee" yaml:"stability_fee"` // per second stability fee for loans opened using this collateral
Prefix byte `json:"prefix" yaml:"prefix"` AuctionSize sdk.Int // Max amount of collateral to sell off in any one auction.
MarketID string `json:"market_id" yaml:"market_id"` // marketID for fetching price of the asset from the pricefeed LiquidationPenalty sdk.Dec // percentage penalty (between [0, 1]) applied to a cdp if it is liquidated
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` // factor for converting internal units to one base unit of collateral Prefix byte `json:"prefix" yaml:"prefix"`
MarketID string `json:"market_id" yaml:"market_id"` // marketID for fetching price of the asset from the pricefeed
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` // factor for converting internal units to one base unit of collateral
} }
// String implements fmt.Stringer // String implements fmt.Stringer
@ -77,11 +90,13 @@ func (cp CollateralParam) String() string {
Denom: %s Denom: %s
Liquidation Ratio: %s Liquidation Ratio: %s
Stability Fee: %s Stability Fee: %s
Liquidation Penalty: %s
Debt Limit: %s Debt Limit: %s
Auction Size: %s
Prefix: %b Prefix: %b
Market ID: %s Market ID: %s
Conversion Factor: %s`, Conversion Factor: %s`,
cp.Denom, cp.LiquidationRatio, cp.StabilityFee, cp.DebtLimit, cp.Prefix, cp.MarketID, cp.ConversionFactor) cp.Denom, cp.LiquidationRatio, cp.StabilityFee, cp.LiquidationPenalty, cp.DebtLimit, cp.AuctionSize, cp.Prefix, cp.MarketID, cp.ConversionFactor)
} }
// CollateralParams array of CollateralParam // CollateralParams array of CollateralParam
@ -140,6 +155,8 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
{Key: KeyCollateralParams, Value: &p.CollateralParams}, {Key: KeyCollateralParams, Value: &p.CollateralParams},
{Key: KeyDebtParams, Value: &p.DebtParams}, {Key: KeyDebtParams, Value: &p.DebtParams},
{Key: KeyCircuitBreaker, Value: &p.CircuitBreaker}, {Key: KeyCircuitBreaker, Value: &p.CircuitBreaker},
{Key: KeySurplusThreshold, Value: &p.SurplusAuctionThreshold},
{Key: KeyDebtThreshold, Value: &p.DebtAuctionThreshold},
} }
} }
@ -203,6 +220,12 @@ func (p Params) Validate() error {
return fmt.Errorf("collateral debt limit for %s exceeds global debt limit: \n\tglobal debt limit: %s\n\tcollateral debt limits: %s", return fmt.Errorf("collateral debt limit for %s exceeds global debt limit: \n\tglobal debt limit: %s\n\tcollateral debt limits: %s",
cp.Denom, p.GlobalDebtLimit, cp.DebtLimit) cp.Denom, p.GlobalDebtLimit, cp.DebtLimit)
} }
if cp.LiquidationPenalty.LT(sdk.ZeroDec()) || cp.LiquidationPenalty.GT(sdk.OneDec()) {
return fmt.Errorf("liquidation penalty should be between 0 and 1, is %s for %s", cp.LiquidationPenalty, cp.Denom)
}
if !cp.AuctionSize.IsPositive() {
return fmt.Errorf("auction size should be positive, is %s for %s", cp.AuctionSize, cp.Denom)
}
} }
if collateralParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { if collateralParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) {
return fmt.Errorf("collateral debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tcollateral debt limits: %s", return fmt.Errorf("collateral debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tcollateral debt limits: %s",
@ -212,5 +235,12 @@ func (p Params) Validate() error {
if p.GlobalDebtLimit.IsAnyNegative() { if p.GlobalDebtLimit.IsAnyNegative() {
return fmt.Errorf("global debt limit should be positive for all debt tokens, is %s", p.GlobalDebtLimit) return fmt.Errorf("global debt limit should be positive for all debt tokens, is %s", p.GlobalDebtLimit)
} }
if !p.SurplusAuctionThreshold.IsPositive() {
return fmt.Errorf("surplus auction threshold should be positive, is %s", p.SurplusAuctionThreshold)
}
if !p.DebtAuctionThreshold.IsPositive() {
return fmt.Errorf("debt auction threshold should be positive, is %s", p.DebtAuctionThreshold)
}
return nil return nil
} }