apply pricefeed changes to other modules

This commit is contained in:
Kevin Davis 2019-12-04 11:54:53 -05:00
parent bf83a9bf8f
commit 05a99be97b
8 changed files with 165 additions and 208 deletions

View File

@ -38,9 +38,9 @@ func TestApp_CreateModifyDeleteCDP(t *testing.T) {
keeper.SetParams(ctx, params)
keeper.SetGlobalDebt(ctx, sdk.NewInt(0))
ap := pricefeed.Params{
Assets: []pricefeed.Asset{
pricefeed.Asset{
AssetCode: "xrp", BaseAsset: "xrp",
Markets: []pricefeed.Market{
pricefeed.Market{
MarketID: "xrp", BaseAsset: "xrp",
QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true},
},
}

View File

@ -10,8 +10,8 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, data GenesisStat
// 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.GetParams(ctx)
for _, a := range ap.Assets {
collateralMap[a.AssetCode] = 1
for _, m := range ap.Markets {
collateralMap[m.MarketID] = 1
}
for _, col := range data.Params.CollateralParams {

View File

@ -111,9 +111,9 @@ func TestKeeper_ModifyCDP(t *testing.T) {
keeper.SetParams(ctx, defaultParamsSingle())
// setup store state
ap := pricefeed.Params{
Assets: []pricefeed.Asset{
pricefeed.Asset{
AssetCode: "xrp", BaseAsset: "xrp",
Markets: []pricefeed.Market{
pricefeed.Market{
MarketID: "xrp", BaseAsset: "xrp",
QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true},
},
}
@ -186,9 +186,9 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) {
ctx := mapp.BaseApp.NewContext(false, header)
keeper.SetParams(ctx, defaultParamsSingle())
ap := pricefeed.Params{
Assets: []pricefeed.Asset{
pricefeed.Asset{
AssetCode: "xrp", BaseAsset: "xrp",
Markets: []pricefeed.Market{
pricefeed.Market{
MarketID: "xrp", BaseAsset: "xrp",
QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true},
},
}

View File

@ -1,11 +1,14 @@
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 Params `json:"params" yaml:"params"`
CDPs CDPs `json:"cdps" yaml:"cdps"`
Params CdpParams `json:"params" yaml:"params"`
GlobalDebt sdk.Int `json:"global_debt" yaml:"global_debt"`
CDPs CDPs `json:"cdps" yaml:"cdps"`
// don't need to setup CollateralStates as they are created as needed
}

View File

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

View File

@ -3,7 +3,6 @@ package types
import (
"fmt"
"strings"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -16,41 +15,27 @@ 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.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
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
}
// 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.AmountOf(cdp.CollateralDenom)).Mul(price)
minCollateralValue := sdk.NewDec(0)
for _, c := range cdp.Debt {
minCollateralValue = minCollateralValue.Add(liquidationRatio.Mul(c.Amount.ToDec()))
}
collateralValue := sdk.NewDecFromInt(cdp.CollateralAmount).Mul(price)
minCollateralValue := liquidationRatio.Mul(sdk.NewDecFromInt(cdp.Debt))
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 Type: %s
Collateral: %s
Debt: %s
Fees: %s
Fees Last Updated: %s`,
Collateral: %s
Debt: %s`,
cdp.Owner,
cdp.CollateralDenom,
cdp.CollateralAmount,
cdp.Debt,
cdp.AccumulatedFees,
cdp.FeesUpdated,
sdk.NewCoin(cdp.CollateralDenom, cdp.CollateralAmount),
sdk.NewCoin("usdx", cdp.Debt),
))
}
// CDPs array of CDP
type CDPs []CDP
// String implements stringer
@ -67,23 +52,22 @@ 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 {

View File

@ -2,10 +2,12 @@ package keeper
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/stretchr/testify/require"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/liquidator/types"
@ -19,8 +21,8 @@ func TestKeeper_SeizeAndStartCollateralAuction(t *testing.T) {
_, addrs := mock.GeneratePrivKeyAddressPairs(1)
pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis())
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), i(999999999))
k.pricefeedKeeper.SetCurrentPrices(ctx)
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), tmtime.Now().Add(time.Hour*1))
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis())
dp := defaultParams()
k.liquidatorKeeper.SetParams(ctx, dp)
@ -28,8 +30,8 @@ func TestKeeper_SeizeAndStartCollateralAuction(t *testing.T) {
k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), i(999999999))
k.pricefeedKeeper.SetCurrentPrices(ctx)
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), tmtime.Now().Add(time.Hour*1))
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
// Run test function
auctionID, err := k.liquidatorKeeper.SeizeAndStartCollateralAuction(ctx, addrs[0], "btc")
@ -100,16 +102,16 @@ func TestKeeper_partialSeizeCDP(t *testing.T) {
pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis())
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), i(999999999))
k.pricefeedKeeper.SetCurrentPrices(ctx)
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), tmtime.Now().Add(time.Hour*1))
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
k.bankKeeper.AddCoins(ctx, addrs[0], cs(c("btc", 100)))
cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis())
k.liquidatorKeeper.SetParams(ctx, defaultParams())
k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), i(999999999))
k.pricefeedKeeper.SetCurrentPrices(ctx)
k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), tmtime.Now().Add(time.Hour*1))
k.pricefeedKeeper.SetCurrentPrices(ctx, "btc")
// Run test function
err := k.liquidatorKeeper.partialSeizeCDP(ctx, addrs[0], "btc", i(2), i(10000))

View File

@ -1,6 +1,8 @@
package keeper
import (
"time"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -9,6 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/params"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
tmtime "github.com/tendermint/tendermint/types/time"
dbm "github.com/tendermint/tm-db"
"github.com/kava-labs/kava/x/auction"
@ -75,7 +78,7 @@ func setupTestKeepers() (sdk.Context, keepers) {
bank.DefaultCodespace,
blacklistedAddrs,
)
pricefeedKeeper := pricefeed.NewKeeper(keyPriceFeed, cdc, paramsKeeper.Subspace(pricefeed.DefaultParamspace).WithKeyTable(pricefeed.ParamKeyTable()), pricefeed.DefaultCodespace)
pricefeedKeeper := pricefeed.NewKeeper(keyPriceFeed, cdc, paramsKeeper.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace)
cdpKeeper := cdp.NewKeeper(
cdc,
keyCDP,
@ -150,20 +153,18 @@ func cdpDefaultGenesis() cdp.GenesisState {
}
func pricefeedGenesis() pricefeed.GenesisState {
ap := pricefeed.AssetParams{
Assets: []pricefeed.Asset{
pricefeed.Asset{AssetCode: "btc", Description: "a description"},
},
ap := pricefeed.Params{
Markets: []pricefeed.Market{
pricefeed.Market{MarketID: "btc", BaseAsset: "btc", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}},
}
return pricefeed.GenesisState{
AssetParams: ap,
OracleParams: pricefeed.DefaultOracleParams(),
Params: ap,
PostedPrices: []pricefeed.PostedPrice{
pricefeed.PostedPrice{
AssetCode: "btc",
OracleAddress: "",
MarketID: "btc",
OracleAddress: sdk.AccAddress{},
Price: sdk.MustNewDecFromStr("8000.00"),
Expiry: sdk.NewInt(999999999),
Expiry: tmtime.Now().Add(1 * time.Hour),
},
},
}