diff --git a/x/cdp/integration_test.go b/x/cdp/integration_test.go index 7cd40fcf..ef648956 100644 --- a/x/cdp/integration_test.go +++ b/x/cdp/integration_test.go @@ -59,7 +59,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { { Denom: "usdx", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, @@ -133,14 +132,12 @@ func NewCDPGenStateMulti() app.GenesisState { { Denom: "usdx", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, { Denom: "susd", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("susd", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, @@ -189,14 +186,10 @@ func badGenStates() []badGenState { g6 := baseGenState() g6.Params.DebtParams[0].Denom = "susd" - g7 := baseGenState() - g7.Params.DebtParams[0].DebtLimit = sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000001)) - g8 := baseGenState() g8.Params.DebtParams = append(g8.Params.DebtParams, cdp.DebtParam{ Denom: "lol", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("lol", 1000000000000)), }) g9 := baseGenState() @@ -221,7 +214,6 @@ func badGenStates() []badGenState { badGenState{Genesis: g4, Reason: "single collateral exceeds debt limit"}, badGenState{Genesis: g5, Reason: "combined collateral exceeds debt limit"}, badGenState{Genesis: g6, Reason: "duplicate debt denom"}, - badGenState{Genesis: g7, Reason: "debt limit exceeds 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: g10, Reason: "previous block time not set"}, @@ -261,14 +253,12 @@ func baseGenState() cdp.GenesisState { { Denom: "usdx", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, { Denom: "susd", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("susd", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, diff --git a/x/cdp/keeper/cdp.go b/x/cdp/keeper/cdp.go index 335cae6a..7a342990 100644 --- a/x/cdp/keeper/cdp.go +++ b/x/cdp/keeper/cdp.go @@ -350,9 +350,6 @@ func (k Keeper) ValidatePrincipalAdd(ctx sdk.Context, principal sdk.Coins) sdk.E if !found { return types.ErrDebtNotSupported(k.codespace, dc.Denom) } - if sdk.NewCoins(dc).IsAnyGT(dp.DebtLimit) { - return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(dc), dp.DebtLimit) - } if dc.Amount.LT(dp.DebtFloor) { return types.ErrBelowDebtFloor(k.codespace, sdk.NewCoins(dc), dp.DebtFloor) } @@ -363,12 +360,21 @@ func (k Keeper) ValidatePrincipalAdd(ctx sdk.Context, principal sdk.Coins) sdk.E // ValidatePrincipalDraw validates that an asset is valid for use as debt when drawing debt off an existing cdp func (k Keeper) ValidatePrincipalDraw(ctx sdk.Context, principal sdk.Coins) sdk.Error { for _, dc := range principal { - dp, found := k.GetDebt(ctx, dc.Denom) + _, found := k.GetDebt(ctx, dc.Denom) if !found { return types.ErrDebtNotSupported(k.codespace, dc.Denom) } - if sdk.NewCoins(dc).IsAnyGT(dp.DebtLimit) { - return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(dc), dp.DebtLimit) + } + return nil +} + +// ValidateDebtLimit validates that the input debt amount does not exceed the global debt limit +func (k Keeper) ValidateDebtLimit(ctx sdk.Context, collateralDenom string, principal sdk.Coins) sdk.Error { + for _, dc := range principal { + totalPrincipal := k.GetTotalPrincipal(ctx, collateralDenom, dc.Denom).Add(dc.Amount) + globalLimit := k.GetParams(ctx).GlobalDebtLimit.AmountOf(dc.Denom) + if totalPrincipal.GT(globalLimit) { + return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, globalLimit))) } } return nil diff --git a/x/cdp/keeper/cdp_test.go b/x/cdp/keeper/cdp_test.go index 26d93857..89e35136 100644 --- a/x/cdp/keeper/cdp_test.go +++ b/x/cdp/keeper/cdp_test.go @@ -256,8 +256,11 @@ func (suite *CdpTestSuite) TestValidatePrincipal() { err = suite.keeper.ValidatePrincipalAdd(suite.ctx, d) suite.Equal(types.CodeDebtNotSupported, err.Result().Code) d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(1000000000001))) - err = suite.keeper.ValidatePrincipalAdd(suite.ctx, d) + err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d) suite.Equal(types.CodeExceedsDebtLimit, err.Result().Code) + d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(100000000))) + err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d) + suite.NoError(err) } func (suite *CdpTestSuite) TestCalculateCollateralizationRatio() { diff --git a/x/cdp/keeper/draw.go b/x/cdp/keeper/draw.go index 1bc1ac66..f286e45a 100644 --- a/x/cdp/keeper/draw.go +++ b/x/cdp/keeper/draw.go @@ -19,6 +19,11 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string return err } + err = k.ValidateDebtLimit(ctx, cdp.Collateral[0].Denom, principal) + if err != nil { + return err + } + // fee calculation 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) diff --git a/x/cdp/keeper/integration_test.go b/x/cdp/keeper/integration_test.go index 78e1c980..7b149f35 100644 --- a/x/cdp/keeper/integration_test.go +++ b/x/cdp/keeper/integration_test.go @@ -59,7 +59,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { { Denom: "usdx", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, @@ -133,14 +132,12 @@ func NewCDPGenStateMulti() app.GenesisState { { Denom: "usdx", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, { Denom: "susd", ReferenceAsset: "usd", - DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("susd", 1000000000000)), ConversionFactor: i(6), DebtFloor: i(10000000), }, diff --git a/x/cdp/types/deposit.go b/x/cdp/types/deposit.go index 1cafcb59..4edd5fc8 100644 --- a/x/cdp/types/deposit.go +++ b/x/cdp/types/deposit.go @@ -51,6 +51,7 @@ func (d Deposit) Empty() bool { return d.Equals(Deposit{}) } +// SumCollateral returns the total amount of collateral in the input deposits func (ds Deposits) SumCollateral() (sum sdk.Int) { sum = sdk.ZeroInt() for _, d := range ds { diff --git a/x/cdp/types/params.go b/x/cdp/types/params.go index 190fb97b..5fb33325 100644 --- a/x/cdp/types/params.go +++ b/x/cdp/types/params.go @@ -113,20 +113,18 @@ func (cps CollateralParams) String() string { // 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"` - ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` - DebtFloor sdk.Int `json:"debt_floor" yaml:"debt_floor"` // minimum active loan size, used to prevent dust + Denom string `json:"denom" yaml:"denom"` + ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"` + ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"` + DebtFloor sdk.Int `json:"debt_floor" yaml:"debt_floor"` // minimum active loan size, used to prevent dust } func (dp DebtParam) String() string { return fmt.Sprintf(`Debt: Denom: %s Reference Asset: %s - Debt Limit: %s Conversion Factor: %s - Debt Floor %s`, dp.Denom, dp.ReferenceAsset, dp.DebtLimit, dp.ConversionFactor, dp.DebtFloor) + Debt Floor %s`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor) } // DebtParams array of DebtParam @@ -164,25 +162,13 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs { func (p Params) Validate() error { // validate debt params 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.DenomsSubsetOf(p.GlobalDebtLimit) { - return fmt.Errorf("debt denom not found in global debt limit:\n\tglobal debt limit: %s\n\tdebt limits: %s", - p.GlobalDebtLimit, debtParamsDebtLimit) - } - if debtParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { - return fmt.Errorf("debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tdebt limits: %s", - p.GlobalDebtLimit, debtParamsDebtLimit) + } // validate collateral params