mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 23:35:19 +00:00
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:
parent
ba80b508ab
commit
9b1bf55be7
3
.gitignore
vendored
3
.gitignore
vendored
@ -14,3 +14,6 @@
|
|||||||
# Exclude build files
|
# Exclude build files
|
||||||
vendor
|
vendor
|
||||||
build
|
build
|
||||||
|
|
||||||
|
# IDE files
|
||||||
|
*.vscode
|
15
app/app.go
15
app/app.go
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -40,12 +40,16 @@ 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
|
||||||
|
LiquidationPenalty: d("0.05"),
|
||||||
|
AuctionSize: i(1000000000),
|
||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
MarketID: asset + ":usd",
|
MarketID: asset + ":usd",
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
@ -98,12 +103,16 @@ 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
|
||||||
|
LiquidationPenalty: d("0.05"),
|
||||||
|
AuctionSize: i(7000000000),
|
||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
MarketID: "xrp:usd",
|
MarketID: "xrp:usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
@ -113,6 +122,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
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
|
||||||
|
LiquidationPenalty: d("0.025"),
|
||||||
|
AuctionSize: i(10000000),
|
||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
MarketID: "btc:usd",
|
MarketID: "btc:usd",
|
||||||
ConversionFactor: i(8),
|
ConversionFactor: i(8),
|
||||||
@ -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,6 +225,9 @@ 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"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,6 +235,8 @@ 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
231
x/cdp/keeper/auctions.go
Normal 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
|
||||||
|
}
|
76
x/cdp/keeper/auctions_test.go
Normal file
76
x/cdp/keeper/auctions_test.go
Normal 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))
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,12 +40,16 @@ 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
|
||||||
|
LiquidationPenalty: d("0.05"),
|
||||||
|
AuctionSize: i(100),
|
||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
MarketID: asset + ":usd",
|
MarketID: asset + ":usd",
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
@ -98,12 +103,16 @@ 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
|
||||||
|
LiquidationPenalty: d("0.05"),
|
||||||
|
AuctionSize: i(7000000000),
|
||||||
Prefix: 0x20,
|
Prefix: 0x20,
|
||||||
MarketID: "xrp:usd",
|
MarketID: "xrp:usd",
|
||||||
ConversionFactor: i(6),
|
ConversionFactor: i(6),
|
||||||
@ -113,6 +122,8 @@ func NewCDPGenStateMulti() app.GenesisState {
|
|||||||
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
|
||||||
|
LiquidationPenalty: d("0.025"),
|
||||||
|
AuctionSize: i(10000000),
|
||||||
Prefix: 0x21,
|
Prefix: 0x21,
|
||||||
MarketID: "btc:usd",
|
MarketID: "btc:usd",
|
||||||
ConversionFactor: i(8),
|
ConversionFactor: i(8),
|
||||||
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
@ -15,19 +15,34 @@ 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
|
||||||
deposits := k.GetDeposits(ctx, cdp.ID)
|
|
||||||
for _, dep := range deposits {
|
|
||||||
if !dep.InLiquidation {
|
|
||||||
dep.InLiquidation = true
|
|
||||||
|
|
||||||
|
// Move debt coins from cdp to liquidator account
|
||||||
|
deposits := k.GetDeposits(ctx, cdp.ID)
|
||||||
|
debt := sdk.ZeroInt()
|
||||||
|
for _, pc := range cdp.Principal {
|
||||||
|
debt = debt.Add(pc.Amount)
|
||||||
|
}
|
||||||
|
for _, dc := range cdp.AccumulatedFees {
|
||||||
|
debt = debt.Add(dc.Amount)
|
||||||
|
}
|
||||||
|
debtCoin := sdk.NewCoin(k.GetDebtDenom(ctx), debt)
|
||||||
|
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(debtCoin))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// liquidate deposits and send collateral from cdp to liquidator
|
||||||
|
for _, dep := range deposits {
|
||||||
ctx.EventManager().EmitEvent(
|
ctx.EventManager().EmitEvent(
|
||||||
sdk.NewEvent(
|
sdk.NewEvent(
|
||||||
types.EventTypeCdpLiquidation,
|
types.EventTypeCdpLiquidation,
|
||||||
@ -36,29 +51,15 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) {
|
|||||||
sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)),
|
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)
|
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, dep.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal[0].Denom)
|
||||||
|
|
||||||
// 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 {
|
|
||||||
debtAmt = debtAmt.Add(dc.Amount)
|
|
||||||
}
|
|
||||||
debtCoins := sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), debtAmt))
|
|
||||||
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, debtCoins)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -11,47 +11,19 @@ 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
|
||||||
|
}
|
||||||
|
@ -9,8 +9,10 @@ const (
|
|||||||
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"
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
@ -31,6 +36,8 @@ 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"`
|
||||||
|
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"`
|
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,24 +47,28 @@ 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,
|
||||||
|
DebtAuctionThreshold: debtThreshold,
|
||||||
|
SurplusAuctionThreshold: surplusThreshold,
|
||||||
CircuitBreaker: breaker,
|
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
|
||||||
@ -66,6 +77,8 @@ type CollateralParam struct {
|
|||||||
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
|
||||||
|
AuctionSize sdk.Int // Max amount of collateral to sell off in any one auction.
|
||||||
|
LiquidationPenalty sdk.Dec // percentage penalty (between [0, 1]) applied to a cdp if it is liquidated
|
||||||
Prefix byte `json:"prefix" yaml:"prefix"`
|
Prefix byte `json:"prefix" yaml:"prefix"`
|
||||||
MarketID string `json:"market_id" yaml:"market_id"` // marketID for fetching price of the asset from the pricefeed
|
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
|
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` // factor for converting internal units to one base unit of collateral
|
||||||
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user