wip: cdp params and types

This commit is contained in:
Kevin Davis 2019-11-28 10:53:59 -06:00
parent d5da161dd8
commit e85d2f880b
10 changed files with 219 additions and 167 deletions

View File

@ -2,10 +2,12 @@ package cdp
import ( import (
"testing" "testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/x/pricefeed" "github.com/kava-labs/kava/x/pricefeed"
) )
@ -19,7 +21,7 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) {
mock.SetGenesis(mapp, genAccs) mock.SetGenesis(mapp, genAccs)
mock.CheckBalance(t, mapp, testAddr, cs(c("xrp", 100))) mock.CheckBalance(t, mapp, testAddr, cs(c("xrp", 100)))
// setup pricefeed, TODO can this be shortened a bit? // 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}) mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, header) ctx := mapp.BaseApp.NewContext(false, header)
params := CdpParams{ params := CdpParams{
@ -35,15 +37,19 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) {
} }
keeper.SetParams(ctx, params) keeper.SetParams(ctx, params)
keeper.SetGlobalDebt(ctx, sdk.NewInt(0)) keeper.SetGlobalDebt(ctx, sdk.NewInt(0))
ap := pricefeed.AssetParams{ ap := pricefeed.Params{
Assets: []pricefeed.Asset{pricefeed.Asset{AssetCode: "xrp", Description: ""}}, 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( pfKeeper.SetPrice(
ctx, sdk.AccAddress{}, "xrp", ctx, sdk.AccAddress{}, "xrp",
sdk.MustNewDecFromStr("1.00"), sdk.MustNewDecFromStr("1.00"),
sdk.NewInt(10)) header.Time.Add(time.Hour*1))
pfKeeper.SetCurrentPrices(ctx) pfKeeper.SetCurrentPrices(ctx, "xrp")
mapp.EndBlock(abci.RequestEndBlock{}) mapp.EndBlock(abci.RequestEndBlock{})
mapp.Commit() mapp.Commit()

View File

@ -9,7 +9,7 @@ import (
func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, data GenesisState) { 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 // validate denoms - check that any collaterals in the CdpParams are in the pricefeed, pricefeed needs to initgenesis before cdp
collateralMap := make(map[string]int) collateralMap := make(map[string]int)
ap := pk.GetAssetParams(ctx) ap := pk.GetParams(ctx)
for _, a := range ap.Assets { for _, a := range ap.Assets {
collateralMap[a.AssetCode] = 1 collateralMap[a.AssetCode] = 1
} }

View File

@ -3,6 +3,7 @@ package keeper
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth"
@ -12,6 +13,7 @@ import (
"github.com/kava-labs/kava/x/pricefeed" "github.com/kava-labs/kava/x/pricefeed"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types" 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? // 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}) mock.SetGenesis(mapp, []authexported.Account{&genAcc})
// create a new context // 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}) mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, header) ctx := mapp.BaseApp.NewContext(false, header)
keeper.SetParams(ctx, defaultParamsSingle()) keeper.SetParams(ctx, defaultParamsSingle())
// setup store state // setup store state
ap := pricefeed.AssetParams{ ap := pricefeed.Params{
Assets: []pricefeed.Asset{ 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( _, err := keeper.pricefeedKeeper.SetPrice(
ctx, ownerAddr, "xrp", ctx, ownerAddr, "xrp",
sdk.MustNewDecFromStr(tc.price), sdk.MustNewDecFromStr(tc.price),
sdk.NewInt(ctx.BlockHeight()+10)) header.Time.Add(time.Hour*1))
if err != nil { if err != nil {
t.Log("test context height", ctx.BlockHeight()) t.Log("test context height", ctx.BlockHeight())
t.Log(err) t.Log(err)
t.Log(tc.name) t.Log(tc.name)
} }
err = keeper.pricefeedKeeper.SetCurrentPrices(ctx) err = keeper.pricefeedKeeper.SetCurrentPrices(ctx, "xrp")
if err != nil { if err != nil {
t.Log("test context height", ctx.BlockHeight()) t.Log("test context height", ctx.BlockHeight())
t.Log(err) t.Log(err)
@ -144,9 +148,6 @@ func TestKeeper_ModifyCDP(t *testing.T) {
// get new state for verification // get new state for verification
actualCDP, found := keeper.GetCDP(ctx, tc.args.owner, tc.args.collateralDenom) actualCDP, found := keeper.GetCDP(ctx, tc.args.owner, tc.args.collateralDenom)
if tc.name == "removeTooMuchCollateral" {
t.Log(actualCDP.String())
}
// check for err // check for err
if tc.expectPass { if tc.expectPass {
@ -180,21 +181,23 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) {
testAddr := addrs[0] testAddr := addrs[0]
mock.SetGenesis(mapp, genAccs) mock.SetGenesis(mapp, genAccs)
// setup pricefeed // 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}) mapp.BeginBlock(abci.RequestBeginBlock{Header: header})
ctx := mapp.BaseApp.NewContext(false, header) ctx := mapp.BaseApp.NewContext(false, header)
keeper.SetParams(ctx, defaultParamsSingle()) keeper.SetParams(ctx, defaultParamsSingle())
ap := pricefeed.AssetParams{ ap := pricefeed.Params{
Assets: []pricefeed.Asset{ 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( keeper.pricefeedKeeper.SetPrice(
ctx, sdk.AccAddress{}, collateral, ctx, sdk.AccAddress{}, collateral,
sdk.MustNewDecFromStr("1.00"), sdk.MustNewDecFromStr("1.00"),
i(10)) header.Time.Add(time.Hour*1))
keeper.pricefeedKeeper.SetCurrentPrices(ctx) keeper.pricefeedKeeper.SetCurrentPrices(ctx, collateral)
// Create CDP // Create CDP
keeper.SetGlobalDebt(ctx, i(0)) keeper.SetGlobalDebt(ctx, i(0))
err := keeper.ModifyCDP(ctx, testAddr, collateral, i(10), i(5)) err := keeper.ModifyCDP(ctx, testAddr, collateral, i(10), i(5))
@ -203,8 +206,8 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) {
keeper.pricefeedKeeper.SetPrice( keeper.pricefeedKeeper.SetPrice(
ctx, sdk.AccAddress{}, collateral, ctx, sdk.AccAddress{}, collateral,
sdk.MustNewDecFromStr("0.90"), sdk.MustNewDecFromStr("0.90"),
i(10)) header.Time.Add(time.Hour*1))
keeper.pricefeedKeeper.SetCurrentPrices(ctx) keeper.pricefeedKeeper.SetCurrentPrices(ctx, collateral)
// Seize entire CDP // Seize entire CDP
err = keeper.PartialSeizeCDP(ctx, testAddr, collateral, i(10), i(5)) err = keeper.PartialSeizeCDP(ctx, testAddr, collateral, i(10), i(5))

View File

@ -30,7 +30,7 @@ func setUpMockAppWithoutGenesis() (*mock.App, Keeper, []sdk.AccAddress, []crypto
keyCDP := sdk.NewKVStoreKey("cdp") keyCDP := sdk.NewKVStoreKey("cdp")
keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey)
pk := mapp.ParamsKeeper 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) blacklistedAddrs := make(map[string]bool)
bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs)
cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(types.DefaultParamspace), priceFeedKeeper, bankKeeper) cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(types.DefaultParamspace), priceFeedKeeper, bankKeeper)

View File

@ -28,7 +28,7 @@ func setUpMockAppWithoutGenesis() (*mock.App, Keeper, PricefeedKeeper) {
keyCDP := sdk.NewKVStoreKey("cdp") keyCDP := sdk.NewKVStoreKey("cdp")
keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey)
pk := mapp.ParamsKeeper 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) blacklistedAddrs := make(map[string]bool)
bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs)
cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(DefaultParamspace), priceFeedKeeper, bankKeeper) cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(DefaultParamspace), priceFeedKeeper, bankKeeper)

View File

@ -1,6 +1,8 @@
package types package types
import ( import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
pftypes "github.com/kava-labs/kava/x/pricefeed/types" pftypes "github.com/kava-labs/kava/x/pricefeed/types"
) )
@ -14,9 +16,9 @@ type BankKeeper interface {
type PricefeedKeeper interface { type PricefeedKeeper interface {
GetCurrentPrice(sdk.Context, string) pftypes.CurrentPrice 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 // These are used for testing TODO replace mockApp with keeper in tests to remove these
SetAssetParams(sdk.Context, pftypes.AssetParams) SetParams(sdk.Context, pftypes.Params)
SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, sdk.Int) (pftypes.PostedPrice, sdk.Error) SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, sdk.Error)
SetCurrentPrices(sdk.Context) sdk.Error SetCurrentPrices(sdk.Context, string) sdk.Error
} }

View File

@ -1,15 +1,10 @@
package types package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// GenesisState is the state that must be provided at genesis. // 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 // TODO What is globaldebt and is is separate from the global debt limit in CdpParams
type GenesisState struct { type GenesisState struct {
Params CdpParams `json:"params" yaml:"params"` Params Params `json:"params" yaml:"params"`
GlobalDebt sdk.Int `json:"global_debt" yaml:"global_debt"`
CDPs CDPs `json:"cdps" yaml:"cdps"` CDPs CDPs `json:"cdps" yaml:"cdps"`
// don't need to setup CollateralStates as they are created as needed // don't need to setup CollateralStates as they are created as needed
} }
@ -19,7 +14,6 @@ type GenesisState struct {
func DefaultGenesisState() GenesisState { func DefaultGenesisState() GenesisState {
return GenesisState{ return GenesisState{
Params: DefaultParams(), Params: DefaultParams(),
GlobalDebt: sdk.ZeroInt(),
CDPs: CDPs{}, CDPs: CDPs{},
} }
} }

View File

@ -4,135 +4,166 @@ import (
"fmt" "fmt"
sdk "github.com/cosmos/cosmos-sdk/types" 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 // Parameter keys
var ( var (
// ParamStoreKeyAuctionParams Param store key for auction params // ParamStoreKeyAuctionParams Param store key for auction params
KeyGlobalDebtLimit = []byte("GlobalDebtLimit") KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
KeyCollateralParams = []byte("CollateralParams") KeyCollateralParams = []byte("CollateralParams")
KeyStableDenoms = []byte("StableDenoms") 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 // ParamKeyTable Key declaration for parameters
func ParamKeyTable() subspace.KeyTable { func ParamKeyTable() params.KeyTable {
return subspace.NewKeyTable().RegisterParamSet(&CdpParams{}) return params.NewKeyTable().RegisterParamSet(&Params{})
} }
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
// pairs of auth module's parameters. // pairs of auth module's parameters.
// nolint // nolint
func (p *CdpParams) ParamSetPairs() subspace.ParamSetPairs { func (p *Params) ParamSetPairs() params.ParamSetPairs {
return subspace.ParamSetPairs{ return params.ParamSetPairs{
{KeyGlobalDebtLimit, &p.GlobalDebtLimit}, {KeyGlobalDebtLimit, &p.GlobalDebtLimit},
{KeyCollateralParams, &p.CollateralParams}, {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. // 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) collateralDupMap := make(map[string]int)
denomDupMap := make(map[string]int) collateralParamsDebtLimit := sdk.Coins{}
for _, collateral := range p.CollateralParams { for _, cp := range p.CollateralParams {
_, found := collateralDupMap[collateral.Denom] _, found := collateralDupMap[cp.Denom]
if found { 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() { if cp.DebtLimit.IsAnyNegative() {
return fmt.Errorf("debt limit should be positive, is %s for %s", collateral.DebtLimit, collateral.Denom) return fmt.Errorf("debt limit for all collaterals should be positive, is %s for %s", cp.DebtLimit, cp.Denom)
}
collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit)
}
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)
} }
// TODO do we want to enforce overcollateralization at this level? -- probably not, as it's technically a governance thing (kevin) if p.GlobalDebtLimit.IsAnyNegative() {
} return fmt.Errorf("global debt limit should be positive for all debt tokens, is %s", p.GlobalDebtLimit)
if p.GlobalDebtLimit.IsNegative() {
return fmt.Errorf("global debt limit should be positive, is %s", p.GlobalDebtLimit)
}
for _, denom := range p.StableDenoms {
_, found := denomDupMap[denom]
if found {
return fmt.Errorf("duplicate stable denom: %s", denom)
}
denomDupMap[denom] = 1
} }
return nil return nil
} }
func DefaultParams() CdpParams {
return CdpParams{
GlobalDebtLimit: sdk.NewInt(0),
CollateralParams: []CollateralParams{},
StableDenoms: []string{"usdx"},
}
}

View File

@ -3,6 +3,7 @@ package types
import ( import (
"fmt" "fmt"
"strings" "strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
) )
@ -15,27 +16,41 @@ type CDP struct {
//ID []byte // removing IDs for now to make things simpler //ID []byte // removing IDs for now to make things simpler
Owner sdk.AccAddress `json:"owner" yaml:"owner"` // Account that authorizes changes to the CDP 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 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 CollateralAmount sdk.Coins `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 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 { func (cdp CDP) IsUnderCollateralized(price sdk.Dec, liquidationRatio sdk.Dec) bool {
collateralValue := sdk.NewDecFromInt(cdp.CollateralAmount).Mul(price) collateralValue := sdk.NewDecFromInt(cdp.CollateralAmount.AmountOf(cdp.CollateralDenom)).Mul(price)
minCollateralValue := liquidationRatio.Mul(sdk.NewDecFromInt(cdp.Debt)) 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? return collateralValue.LT(minCollateralValue) // TODO LT or LTE?
} }
// String implements fmt.stringer
func (cdp CDP) String() string { func (cdp CDP) String() string {
return strings.TrimSpace(fmt.Sprintf(`CDP: return strings.TrimSpace(fmt.Sprintf(`CDP:
Owner: %s Owner: %s
Collateral Type: %s
Collateral: %s Collateral: %s
Debt: %s`, Debt: %s
Fees: %s
Fees Last Updated: %s`,
cdp.Owner, cdp.Owner,
sdk.NewCoin(cdp.CollateralDenom, cdp.CollateralAmount), cdp.CollateralDenom,
sdk.NewCoin("usdx", cdp.Debt), cdp.CollateralAmount,
cdp.Debt,
cdp.AccumulatedFees,
cdp.FeesUpdated,
)) ))
} }
// CDPs array of CDP
type CDPs []CDP type CDPs []CDP
// String implements stringer // String implements stringer
@ -52,22 +67,23 @@ type ByCollateralRatio CDPs
func (cdps ByCollateralRatio) Len() int { return len(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) 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 // func (cdps ByCollateralRatio) Less(i, j int) bool {
// The comparison is: collat_i/debt_i < collat_j/debt_j // // Sort by "collateral ratio" ie collateralAmount/Debt
// But to avoid division this can be rearranged to: collat_i*debt_j < collat_j*debt_i // // The comparison is: collat_i/debt_i < collat_j/debt_j
// Provided the values are positive, so check for positive values. // // But to avoid division this can be rearranged to: collat_i*debt_j < collat_j*debt_i
if cdps[i].CollateralAmount.IsNegative() || // // Provided the values are positive, so check for positive values.
cdps[i].Debt.IsNegative() || // if cdps[i].CollateralAmount.IsNegative() ||
cdps[j].CollateralAmount.IsNegative() || // cdps[i].Debt.IsNegative() ||
cdps[j].Debt.IsNegative() { // cdps[j].CollateralAmount.IsNegative() ||
panic("negative collateral and debt not supported in CDPs") // 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) // // TODO overflows could cause panics
right := cdps[j].CollateralAmount.Mul(cdps[i].Debt) // left := cdps[i].CollateralAmount.Mul(cdps[j].Debt)
return left.LT(right) // right := cdps[j].CollateralAmount.Mul(cdps[i].Debt)
} // return left.LT(right)
// }
// CollateralState stores global information tied to a particular collateral type. // CollateralState stores global information tied to a particular collateral type.
type CollateralState struct { type CollateralState struct {

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
params "github.com/cosmos/cosmos-sdk/x/params/subspace" "github.com/cosmos/cosmos-sdk/x/params"
) )
var ( var (