From e85d2f880b326b2f79724fc2465d44f8cc0f7a33 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 28 Nov 2019 10:53:59 -0600 Subject: [PATCH] wip: cdp params and types --- x/cdp/app_test.go | 18 ++- x/cdp/genesis.go | 2 +- x/cdp/keeper/keeper_test.go | 37 ++--- x/cdp/keeper/test_common.go | 2 +- x/cdp/test_common.go | 2 +- x/cdp/types/expected_keepers.go | 10 +- x/cdp/types/genesis.go | 14 +- x/cdp/types/params.go | 235 ++++++++++++++++++-------------- x/cdp/types/types.go | 64 +++++---- x/pricefeed/types/params.go | 2 +- 10 files changed, 219 insertions(+), 167 deletions(-) diff --git a/x/cdp/app_test.go b/x/cdp/app_test.go index 39737d43..fed5e0f0 100644 --- a/x/cdp/app_test.go +++ b/x/cdp/app_test.go @@ -2,10 +2,12 @@ package cdp import ( "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" "github.com/kava-labs/kava/x/pricefeed" ) @@ -19,7 +21,7 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) { mock.SetGenesis(mapp, genAccs) mock.CheckBalance(t, mapp, testAddr, cs(c("xrp", 100))) // setup pricefeed, TODO can this be shortened a bit? - header := abci.Header{Height: mapp.LastBlockHeight() + 1} + header := abci.Header{Height: mapp.LastBlockHeight() + 1, Time: tmtime.Now()} mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, header) params := CdpParams{ @@ -35,15 +37,19 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) { } keeper.SetParams(ctx, params) keeper.SetGlobalDebt(ctx, sdk.NewInt(0)) - ap := pricefeed.AssetParams{ - Assets: []pricefeed.Asset{pricefeed.Asset{AssetCode: "xrp", Description: ""}}, + ap := pricefeed.Params{ + Assets: []pricefeed.Asset{ + pricefeed.Asset{ + AssetCode: "xrp", BaseAsset: "xrp", + QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + }, } - pfKeeper.SetAssetParams(ctx, ap) + pfKeeper.SetParams(ctx, ap) pfKeeper.SetPrice( ctx, sdk.AccAddress{}, "xrp", sdk.MustNewDecFromStr("1.00"), - sdk.NewInt(10)) - pfKeeper.SetCurrentPrices(ctx) + header.Time.Add(time.Hour*1)) + pfKeeper.SetCurrentPrices(ctx, "xrp") mapp.EndBlock(abci.RequestEndBlock{}) mapp.Commit() diff --git a/x/cdp/genesis.go b/x/cdp/genesis.go index 5bd639fa..d50be231 100644 --- a/x/cdp/genesis.go +++ b/x/cdp/genesis.go @@ -9,7 +9,7 @@ import ( func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, data GenesisState) { // validate denoms - check that any collaterals in the CdpParams are in the pricefeed, pricefeed needs to initgenesis before cdp collateralMap := make(map[string]int) - ap := pk.GetAssetParams(ctx) + ap := pk.GetParams(ctx) for _, a := range ap.Assets { collateralMap[a.AssetCode] = 1 } diff --git a/x/cdp/keeper/keeper_test.go b/x/cdp/keeper/keeper_test.go index 17f03000..df2b3c0b 100644 --- a/x/cdp/keeper/keeper_test.go +++ b/x/cdp/keeper/keeper_test.go @@ -3,6 +3,7 @@ package keeper import ( "fmt" "testing" + "time" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -12,6 +13,7 @@ import ( "github.com/kava-labs/kava/x/pricefeed" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" ) // How could one reduce the number of params in the test cases. Create a table driven test for each of the 4 add/withdraw collateral/debt? @@ -103,27 +105,29 @@ func TestKeeper_ModifyCDP(t *testing.T) { } mock.SetGenesis(mapp, []authexported.Account{&genAcc}) // create a new context - header := abci.Header{Height: mapp.LastBlockHeight() + 1} + header := abci.Header{Height: mapp.LastBlockHeight() + 1, Time: tmtime.Now()} mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, header) keeper.SetParams(ctx, defaultParamsSingle()) // setup store state - ap := pricefeed.AssetParams{ + ap := pricefeed.Params{ Assets: []pricefeed.Asset{ - pricefeed.Asset{AssetCode: "xrp", Description: ""}, + pricefeed.Asset{ + AssetCode: "xrp", BaseAsset: "xrp", + QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, }, } - keeper.pricefeedKeeper.SetAssetParams(ctx, ap) + keeper.pricefeedKeeper.SetParams(ctx, ap) _, err := keeper.pricefeedKeeper.SetPrice( ctx, ownerAddr, "xrp", sdk.MustNewDecFromStr(tc.price), - sdk.NewInt(ctx.BlockHeight()+10)) + header.Time.Add(time.Hour*1)) if err != nil { t.Log("test context height", ctx.BlockHeight()) t.Log(err) t.Log(tc.name) } - err = keeper.pricefeedKeeper.SetCurrentPrices(ctx) + err = keeper.pricefeedKeeper.SetCurrentPrices(ctx, "xrp") if err != nil { t.Log("test context height", ctx.BlockHeight()) t.Log(err) @@ -144,9 +148,6 @@ func TestKeeper_ModifyCDP(t *testing.T) { // get new state for verification actualCDP, found := keeper.GetCDP(ctx, tc.args.owner, tc.args.collateralDenom) - if tc.name == "removeTooMuchCollateral" { - t.Log(actualCDP.String()) - } // check for err if tc.expectPass { @@ -180,21 +181,23 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) { testAddr := addrs[0] mock.SetGenesis(mapp, genAccs) // setup pricefeed - header := abci.Header{Height: mapp.LastBlockHeight() + 1} + header := abci.Header{Height: mapp.LastBlockHeight() + 1, Time: tmtime.Now()} mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) ctx := mapp.BaseApp.NewContext(false, header) keeper.SetParams(ctx, defaultParamsSingle()) - ap := pricefeed.AssetParams{ + ap := pricefeed.Params{ Assets: []pricefeed.Asset{ - pricefeed.Asset{AssetCode: "xrp", Description: ""}, + pricefeed.Asset{ + AssetCode: "xrp", BaseAsset: "xrp", + QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, }, } - keeper.pricefeedKeeper.SetAssetParams(ctx, ap) + keeper.pricefeedKeeper.SetParams(ctx, ap) keeper.pricefeedKeeper.SetPrice( ctx, sdk.AccAddress{}, collateral, sdk.MustNewDecFromStr("1.00"), - i(10)) - keeper.pricefeedKeeper.SetCurrentPrices(ctx) + header.Time.Add(time.Hour*1)) + keeper.pricefeedKeeper.SetCurrentPrices(ctx, collateral) // Create CDP keeper.SetGlobalDebt(ctx, i(0)) err := keeper.ModifyCDP(ctx, testAddr, collateral, i(10), i(5)) @@ -203,8 +206,8 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) { keeper.pricefeedKeeper.SetPrice( ctx, sdk.AccAddress{}, collateral, sdk.MustNewDecFromStr("0.90"), - i(10)) - keeper.pricefeedKeeper.SetCurrentPrices(ctx) + header.Time.Add(time.Hour*1)) + keeper.pricefeedKeeper.SetCurrentPrices(ctx, collateral) // Seize entire CDP err = keeper.PartialSeizeCDP(ctx, testAddr, collateral, i(10), i(5)) diff --git a/x/cdp/keeper/test_common.go b/x/cdp/keeper/test_common.go index 474b069e..59619e51 100644 --- a/x/cdp/keeper/test_common.go +++ b/x/cdp/keeper/test_common.go @@ -30,7 +30,7 @@ func setUpMockAppWithoutGenesis() (*mock.App, Keeper, []sdk.AccAddress, []crypto keyCDP := sdk.NewKVStoreKey("cdp") keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) pk := mapp.ParamsKeeper - priceFeedKeeper := pricefeed.NewKeeper(keyPriceFeed, mapp.Cdc, pk.Subspace(pricefeed.DefaultParamspace).WithKeyTable(pricefeed.ParamKeyTable()), pricefeed.DefaultCodespace) + priceFeedKeeper := pricefeed.NewKeeper(keyPriceFeed, mapp.Cdc, pk.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace) blacklistedAddrs := make(map[string]bool) bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(types.DefaultParamspace), priceFeedKeeper, bankKeeper) diff --git a/x/cdp/test_common.go b/x/cdp/test_common.go index 54066f56..5d537315 100644 --- a/x/cdp/test_common.go +++ b/x/cdp/test_common.go @@ -28,7 +28,7 @@ func setUpMockAppWithoutGenesis() (*mock.App, Keeper, PricefeedKeeper) { keyCDP := sdk.NewKVStoreKey("cdp") keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) pk := mapp.ParamsKeeper - priceFeedKeeper := pricefeed.NewKeeper(keyPriceFeed, mapp.Cdc, pk.Subspace(pricefeed.DefaultParamspace).WithKeyTable(pricefeed.ParamKeyTable()), pricefeed.DefaultCodespace) + priceFeedKeeper := pricefeed.NewKeeper(keyPriceFeed, mapp.Cdc, pk.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace) blacklistedAddrs := make(map[string]bool) bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(DefaultParamspace), priceFeedKeeper, bankKeeper) diff --git a/x/cdp/types/expected_keepers.go b/x/cdp/types/expected_keepers.go index fe41bed2..41562789 100644 --- a/x/cdp/types/expected_keepers.go +++ b/x/cdp/types/expected_keepers.go @@ -1,6 +1,8 @@ package types import ( + "time" + sdk "github.com/cosmos/cosmos-sdk/types" pftypes "github.com/kava-labs/kava/x/pricefeed/types" ) @@ -14,9 +16,9 @@ type BankKeeper interface { type PricefeedKeeper interface { GetCurrentPrice(sdk.Context, string) pftypes.CurrentPrice - GetAssetParams(sdk.Context) pftypes.AssetParams + GetParams(sdk.Context) pftypes.Params // These are used for testing TODO replace mockApp with keeper in tests to remove these - SetAssetParams(sdk.Context, pftypes.AssetParams) - SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, sdk.Int) (pftypes.PostedPrice, sdk.Error) - SetCurrentPrices(sdk.Context) sdk.Error + SetParams(sdk.Context, pftypes.Params) + SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, sdk.Error) + SetCurrentPrices(sdk.Context, string) sdk.Error } diff --git a/x/cdp/types/genesis.go b/x/cdp/types/genesis.go index 96596e46..390f9696 100644 --- a/x/cdp/types/genesis.go +++ b/x/cdp/types/genesis.go @@ -1,16 +1,11 @@ package types -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - // GenesisState is the state that must be provided at genesis. // TODO What is globaldebt and is is separate from the global debt limit in CdpParams type GenesisState struct { - Params CdpParams `json:"params" yaml:"params"` - GlobalDebt sdk.Int `json:"global_debt" yaml:"global_debt"` - CDPs CDPs `json:"cdps" yaml:"cdps"` + Params Params `json:"params" yaml:"params"` + CDPs CDPs `json:"cdps" yaml:"cdps"` // don't need to setup CollateralStates as they are created as needed } @@ -18,9 +13,8 @@ type GenesisState struct { // TODO make this empty, load test values independent func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), - GlobalDebt: sdk.ZeroInt(), - CDPs: CDPs{}, + Params: DefaultParams(), + CDPs: CDPs{}, } } diff --git a/x/cdp/types/params.go b/x/cdp/types/params.go index d4afa0dd..8cee7dcd 100644 --- a/x/cdp/types/params.go +++ b/x/cdp/types/params.go @@ -4,135 +4,166 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/params/subspace" + "github.com/cosmos/cosmos-sdk/x/params" ) -/* -How this uses the sdk params module: - - Put all the params for this module in one struct `CDPModuleParams` - - Store this in the keeper's paramSubspace under one key - - Provide a function to load the param struct all at once `keeper.GetParams(ctx)` -It's possible to set individual key value pairs within a paramSubspace, but reading and setting them is awkward (an empty variable needs to be created, then Get writes the value into it) -This approach will be awkward if we ever need to write individual parameters (because they're stored all together). If this happens do as the sdk modules do - store parameters separately with custom get/set func for each. -*/ - -// CdpParams governance parameters for cdp module -type CdpParams struct { - GlobalDebtLimit sdk.Int - CollateralParams []CollateralParams - StableDenoms []string -} - -// CollateralParams governance parameters for each collateral type within the cdp module -type CollateralParams struct { - Denom string // Coin name of collateral type - LiquidationRatio sdk.Dec // The ratio (Collateral (priced in stable coin) / Debt) under which a CDP will be liquidated - DebtLimit sdk.Int // Maximum amount of debt allowed to be drawn from this collateral type - //DebtFloor sdk.Int // used to prevent dust -} - // Parameter keys var ( // ParamStoreKeyAuctionParams Param store key for auction params - KeyGlobalDebtLimit = []byte("GlobalDebtLimit") - KeyCollateralParams = []byte("CollateralParams") - KeyStableDenoms = []byte("StableDenoms") + KeyGlobalDebtLimit = []byte("GlobalDebtLimit") + KeyCollateralParams = []byte("CollateralParams") + KeyDebtParams = []byte("DebtParams") + DefaultGlobalDebt = sdk.Coins{} + DefaultCircuitBreaker = false + DefaultCollateralParams = CollateralParams{} + DefaultDebtParams = DebtParams{} ) +// Params governance parameters for cdp module +type Params struct { + CollateralParams CollateralParams `json:"collateral_params" yaml:"collateral_params"` + DebtParams DebtParams `json:"debt_params" yaml:"debt_params"` + GlobalDebtLimit sdk.Coins `json:"global_debt_limit" yaml:"global_debt_limit"` + CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"` +} + +// String implements fmt.Stringer +func (p Params) String() string { + return fmt.Sprintf(`Params: + Global Debt Limit: %s + Collateral Params: %s + Debt Params: %s + Circuit Breaker: %t`, + p.GlobalDebtLimit, p.CollateralParams, p.DebtParams, p.CircuitBreaker, + ) +} + +// NewParams returns a new params object +func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParams DebtParams, breaker bool) Params { + return Params{ + GlobalDebtLimit: debtLimit, + CollateralParams: collateralParams, + DebtParams: debtParams, + CircuitBreaker: breaker, + } +} + +// DefaultParams returns default params for cdp module +func DefaultParams() Params { + return NewParams(DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParams, DefaultCircuitBreaker) +} + +// CollateralParam governance parameters for each collateral type within the cdp module +type CollateralParam struct { + Denom string `json:"denom" yaml:"denom"` // Coin name of collateral type + LiquidationRatio sdk.Dec `json:"liquidation_ratio" yaml:"liquidation_ratio"` // The ratio (Collateral (priced in stable coin) / Debt) under which a CDP will be liquidated + DebtLimit sdk.Coins `json:"debt_limit" yaml:"debt_limit"` // Maximum amount of debt allowed to be drawn from this collateral type + //DebtFloor sdk.Int // used to prevent dust +} + +// String implements fmt.Stringer +func (cp CollateralParam) String() string { + return fmt.Sprintf(`Collateral: + Denom: %s + LiquidationRatio: %s + DebtLimit: %s`, cp.Denom, cp.LiquidationRatio, cp.DebtLimit) +} + +// CollateralParams array of CollateralParam +type CollateralParams []CollateralParam + +// String implements fmt.Stringer +func (cps CollateralParams) String() string { + out := "Collateral Params\n" + for _, cp := range cps { + out += fmt.Sprintf("%s\n", cp) + } + return out +} + +// DebtParam governance params for debt assets +type DebtParam struct { + Denom string `json:"denom" yaml:"denom"` + ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"` + DebtLimit sdk.Coins `json:"debt_limit" yaml:"debt_limit"` +} + +func (dp DebtParam) String() string { + return fmt.Sprintf(`Debt: + Denom: %s + ReferenceAsset: %s + DebtLimit: %s`, dp.Denom, dp.ReferenceAsset, dp.DebtLimit) +} + +// DebtParams array of DebtParam +type DebtParams []DebtParam + +// String implements fmt.Stringer +func (dps DebtParams) String() string { + out := "Debt Params\n" + for _, dp := range dps { + out += fmt.Sprintf("%s\n", dp) + } + return out +} + // ParamKeyTable Key declaration for parameters -func ParamKeyTable() subspace.KeyTable { - return subspace.NewKeyTable().RegisterParamSet(&CdpParams{}) +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable().RegisterParamSet(&Params{}) } // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs // pairs of auth module's parameters. // nolint -func (p *CdpParams) ParamSetPairs() subspace.ParamSetPairs { - return subspace.ParamSetPairs{ +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{ {KeyGlobalDebtLimit, &p.GlobalDebtLimit}, {KeyCollateralParams, &p.CollateralParams}, - {KeyStableDenoms, &p.StableDenoms}, + {KeyDebtParams, &p.DebtParams}, } } -// String implements fmt.Stringer -func (p CdpParams) String() string { - out := fmt.Sprintf(`Params: - Global Debt Limit: %s - Collateral Params:`, - p.GlobalDebtLimit, - ) - for _, cp := range p.CollateralParams { - out += fmt.Sprintf(` - %s - Liquidation Ratio: %s - Debt Limit: %s`, - cp.Denom, - cp.LiquidationRatio, - cp.DebtLimit, - ) - } - return out -} - -// GetCollateralParams returns params for a specific collateral denom -func (p CdpParams) GetCollateralParams(collateralDenom string) CollateralParams { - // search for matching denom, return - for _, cp := range p.CollateralParams { - if cp.Denom == collateralDenom { - return cp - } - } - // panic if not found, to be safe - panic("collateral params not found in module params") -} - -// IsCollateralPresent returns true if the denom is among the collaterals in cdp module -func (p CdpParams) IsCollateralPresent(collateralDenom string) bool { - // search for matching denom, return - for _, cp := range p.CollateralParams { - if cp.Denom == collateralDenom { - return true - } - } - return false -} - // Validate checks that the parameters have valid values. -func (p CdpParams) Validate() error { +func (p Params) Validate() error { + debtDenoms := make(map[string]int) + debtParamsDebtLimit := sdk.Coins{} + for _, dp := range p.DebtParams { + _, found := debtDenoms[dp.Denom] + if found { + return fmt.Errorf("duplicate debt denom: %s", dp.Denom) + } + debtDenoms[dp.Denom] = 1 + if dp.DebtLimit.IsAnyNegative() { + return fmt.Errorf("debt limit for all debt tokens should be positive, is %s for %s", dp.DebtLimit, dp.Denom) + } + debtParamsDebtLimit = debtParamsDebtLimit.Add(dp.DebtLimit) + } + if debtParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { + fmt.Errorf("debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tdebt limits: %s", + p.GlobalDebtLimit, debtParamsDebtLimit) + } + collateralDupMap := make(map[string]int) - denomDupMap := make(map[string]int) - for _, collateral := range p.CollateralParams { - _, found := collateralDupMap[collateral.Denom] + collateralParamsDebtLimit := sdk.Coins{} + for _, cp := range p.CollateralParams { + _, found := collateralDupMap[cp.Denom] if found { - return fmt.Errorf("duplicate denom: %s", collateral.Denom) + return fmt.Errorf("duplicate collateral denom: %s", cp.Denom) } - collateralDupMap[collateral.Denom] = 1 + collateralDupMap[cp.Denom] = 1 - if collateral.DebtLimit.IsNegative() { - return fmt.Errorf("debt limit should be positive, is %s for %s", collateral.DebtLimit, collateral.Denom) + if cp.DebtLimit.IsAnyNegative() { + return fmt.Errorf("debt limit for all collaterals should be positive, is %s for %s", cp.DebtLimit, cp.Denom) } - - // TODO do we want to enforce overcollateralization at this level? -- probably not, as it's technically a governance thing (kevin) + collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit) } - if p.GlobalDebtLimit.IsNegative() { - return fmt.Errorf("global debt limit should be positive, is %s", p.GlobalDebtLimit) + if collateralParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { + fmt.Errorf("collateral debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tcollateral debt limits: %s", + p.GlobalDebtLimit, collateralParamsDebtLimit) } - for _, denom := range p.StableDenoms { - _, found := denomDupMap[denom] - if found { - return fmt.Errorf("duplicate stable denom: %s", denom) - } - denomDupMap[denom] = 1 + if p.GlobalDebtLimit.IsAnyNegative() { + return fmt.Errorf("global debt limit should be positive for all debt tokens, is %s", p.GlobalDebtLimit) } return nil } - -func DefaultParams() CdpParams { - return CdpParams{ - GlobalDebtLimit: sdk.NewInt(0), - CollateralParams: []CollateralParams{}, - StableDenoms: []string{"usdx"}, - } -} diff --git a/x/cdp/types/types.go b/x/cdp/types/types.go index 02051ed4..c4b96cbc 100644 --- a/x/cdp/types/types.go +++ b/x/cdp/types/types.go @@ -3,6 +3,7 @@ package types import ( "fmt" "strings" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -15,27 +16,41 @@ type CDP struct { //ID []byte // removing IDs for now to make things simpler Owner sdk.AccAddress `json:"owner" yaml:"owner"` // Account that authorizes changes to the CDP CollateralDenom string `json:"collateral_denom" yaml:"collateral_denom"` // Type of collateral stored in this CDP - CollateralAmount sdk.Int `json:"collateral_amount" yaml:"collateral_amount"` // Amount of collateral stored in this CDP - Debt sdk.Int `json:"debt" yaml:"debt"` // Amount of stable coin drawn from this CDP + CollateralAmount sdk.Coins `json:"collateral_amount" yaml:"collateral_amount"` // Amount of collateral stored in this CDP + Debt sdk.Coins `json:"debt" yaml:"debt"` + AccumulatedFees sdk.Coins `json:"accumulated_fees" yaml:"accumulated_fees"` + FeesUpdated time.Time `json:"fees_updated" yaml:"fees_updated"` // Amount of stable coin drawn from this CDP } +// IsUnderCollateralized checks if cdp is below the liquidation ratio func (cdp CDP) IsUnderCollateralized(price sdk.Dec, liquidationRatio sdk.Dec) bool { - collateralValue := sdk.NewDecFromInt(cdp.CollateralAmount).Mul(price) - minCollateralValue := liquidationRatio.Mul(sdk.NewDecFromInt(cdp.Debt)) + collateralValue := sdk.NewDecFromInt(cdp.CollateralAmount.AmountOf(cdp.CollateralDenom)).Mul(price) + minCollateralValue := sdk.NewDec(0) + for _, c := range cdp.Debt { + minCollateralValue = minCollateralValue.Add(liquidationRatio.Mul(c.Amount.ToDec())) + } return collateralValue.LT(minCollateralValue) // TODO LT or LTE? } +// String implements fmt.stringer func (cdp CDP) String() string { return strings.TrimSpace(fmt.Sprintf(`CDP: Owner: %s - Collateral: %s - Debt: %s`, + Collateral Type: %s + Collateral: %s + Debt: %s + Fees: %s + Fees Last Updated: %s`, cdp.Owner, - sdk.NewCoin(cdp.CollateralDenom, cdp.CollateralAmount), - sdk.NewCoin("usdx", cdp.Debt), + cdp.CollateralDenom, + cdp.CollateralAmount, + cdp.Debt, + cdp.AccumulatedFees, + cdp.FeesUpdated, )) } +// CDPs array of CDP type CDPs []CDP // String implements stringer @@ -52,22 +67,23 @@ type ByCollateralRatio CDPs func (cdps ByCollateralRatio) Len() int { return len(cdps) } func (cdps ByCollateralRatio) Swap(i, j int) { cdps[i], cdps[j] = cdps[j], cdps[i] } -func (cdps ByCollateralRatio) Less(i, j int) bool { - // Sort by "collateral ratio" ie collateralAmount/Debt - // The comparison is: collat_i/debt_i < collat_j/debt_j - // But to avoid division this can be rearranged to: collat_i*debt_j < collat_j*debt_i - // Provided the values are positive, so check for positive values. - if cdps[i].CollateralAmount.IsNegative() || - cdps[i].Debt.IsNegative() || - cdps[j].CollateralAmount.IsNegative() || - cdps[j].Debt.IsNegative() { - panic("negative collateral and debt not supported in CDPs") - } - // TODO overflows could cause panics - left := cdps[i].CollateralAmount.Mul(cdps[j].Debt) - right := cdps[j].CollateralAmount.Mul(cdps[i].Debt) - return left.LT(right) -} + +// func (cdps ByCollateralRatio) Less(i, j int) bool { +// // Sort by "collateral ratio" ie collateralAmount/Debt +// // The comparison is: collat_i/debt_i < collat_j/debt_j +// // But to avoid division this can be rearranged to: collat_i*debt_j < collat_j*debt_i +// // Provided the values are positive, so check for positive values. +// if cdps[i].CollateralAmount.IsNegative() || +// cdps[i].Debt.IsNegative() || +// cdps[j].CollateralAmount.IsNegative() || +// cdps[j].Debt.IsNegative() { +// panic("negative collateral and debt not supported in CDPs") +// } +// // TODO overflows could cause panics +// left := cdps[i].CollateralAmount.Mul(cdps[j].Debt) +// right := cdps[j].CollateralAmount.Mul(cdps[i].Debt) +// return left.LT(right) +// } // CollateralState stores global information tied to a particular collateral type. type CollateralState struct { diff --git a/x/pricefeed/types/params.go b/x/pricefeed/types/params.go index c6be85a7..49d68716 100644 --- a/x/pricefeed/types/params.go +++ b/x/pricefeed/types/params.go @@ -5,7 +5,7 @@ import ( "strings" sdk "github.com/cosmos/cosmos-sdk/types" - params "github.com/cosmos/cosmos-sdk/x/params/subspace" + "github.com/cosmos/cosmos-sdk/x/params" ) var (