Use sdk.Coin in cdp module (#466)

* Use sdk.Coin in cdp module
Co-authored-by: Federico Kunze <federico.kunze94@gmail.com>
Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com>
Co-authored-by: Denali Marsh <denali@kava.io>
Co-authored-by: John Maheswaran <john@noreply>
This commit is contained in:
Kevin Davis 2020-04-27 10:40:34 -04:00 committed by GitHub
parent b969a0ea33
commit ae4aee46ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1609 additions and 1028 deletions

View File

@ -126,8 +126,8 @@ func sendBtcCdp() {
// sender, collateral, principal // sender, collateral, principal
msg := cdp.NewMsgCreateCDP( msg := cdp.NewMsgCreateCDP(
addr, addr,
sdk.NewCoins(sdk.NewInt64Coin("btc", 200000000)), sdk.NewInt64Coin("btc", 200000000),
sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000)), sdk.NewInt64Coin("usdx", 10000000),
) )
// helper methods for transactions // helper methods for transactions
@ -159,8 +159,8 @@ func sendXrpCdp() {
// sender, collateral, principal // sender, collateral, principal
msg := cdp.NewMsgCreateCDP( msg := cdp.NewMsgCreateCDP(
addr, addr,
sdk.NewCoins(sdk.NewInt64Coin("xrp", 200000000)), sdk.NewInt64Coin("xrp", 200000000),
sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000)), sdk.NewInt64Coin("usdx", 10000000),
) )
// helper methods for transactions // helper methods for transactions

View File

@ -56,17 +56,15 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
} }
distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix()) distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix())
if distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) { if distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) {
for _, dp := range params.DebtParams { err := k.DistributeSavingsRate(ctx, params.DebtParam.Denom)
err := k.DistributeSavingsRate(ctx, dp.Denom) if err != nil {
if err != nil { ctx.EventManager().EmitEvent(
ctx.EventManager().EmitEvent( sdk.NewEvent(
sdk.NewEvent( EventTypeBeginBlockerFatal,
EventTypeBeginBlockerFatal, sdk.NewAttribute(sdk.AttributeKeyModule, fmt.Sprintf("%s", ModuleName)),
sdk.NewAttribute(sdk.AttributeKeyModule, fmt.Sprintf("%s", ModuleName)), sdk.NewAttribute(types.AttributeKeyError, fmt.Sprintf("%s", err)),
sdk.NewAttribute(types.AttributeKeyError, fmt.Sprintf("%s", err)), ),
), )
)
}
} }
k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime()) k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime())
} }

View File

@ -100,7 +100,7 @@ func (suite *ModuleTestSuite) createCdps() {
tracker.debt += int64(debt) tracker.debt += int64(debt)
} }
} }
suite.Nil(suite.keeper.AddCdp(suite.ctx, addrs[j], cs(c(collateral, int64(amount))), cs(c("usdx", int64(debt))))) suite.Nil(suite.keeper.AddCdp(suite.ctx, addrs[j], c(collateral, int64(amount)), c("usdx", int64(debt))))
c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1)) c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1))
suite.True(f) suite.True(f)
cdps[j] = c cdps[j] = c
@ -150,7 +150,7 @@ func (suite *ModuleTestSuite) TestBeginBlock() {
} }
func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() { func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() {
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[0], cs(c("xrp", 10000000000)), cs(c("usdx", 1000000000))) err := suite.keeper.AddCdp(suite.ctx, suite.addrs[0], c("xrp", 10000000000), c("usdx", 1000000000))
suite.NoError(err) suite.NoError(err)
suite.Equal(i(1000000000), suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")) suite.Equal(i(1000000000), suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx"))
sk := suite.app.GetSupplyKeeper() sk := suite.app.GetSupplyKeeper()

View File

@ -50,22 +50,6 @@ var (
NewAugmentedCDP = types.NewAugmentedCDP NewAugmentedCDP = types.NewAugmentedCDP
RegisterCodec = types.RegisterCodec RegisterCodec = types.RegisterCodec
NewDeposit = types.NewDeposit NewDeposit = types.NewDeposit
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
ErrCollateralNotSupported = types.ErrCollateralNotSupported
ErrDebtNotSupported = types.ErrDebtNotSupported
ErrExceedsDebtLimit = types.ErrExceedsDebtLimit
ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio
ErrCdpNotFound = types.ErrCdpNotFound
ErrDepositNotFound = types.ErrDepositNotFound
ErrInvalidDeposit = types.ErrInvalidDeposit
ErrInvalidPayment = types.ErrInvalidPayment
ErrDepositNotAvailable = types.ErrDepositNotAvailable
ErrInvalidCollateral = types.ErrInvalidCollateral
ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount
ErrCdpNotAvailable = types.ErrCdpNotAvailable
ErrBelowDebtFloor = types.ErrBelowDebtFloor
ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP
NewGenesisState = types.NewGenesisState NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState DefaultGenesisState = types.DefaultGenesisState
GetCdpIDBytes = types.GetCdpIDBytes GetCdpIDBytes = types.GetCdpIDBytes
@ -102,6 +86,23 @@ var (
// variable aliases // variable aliases
ModuleCdc = types.ModuleCdc ModuleCdc = types.ModuleCdc
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
ErrCollateralNotSupported = types.ErrCollateralNotSupported
ErrDebtNotSupported = types.ErrDebtNotSupported
ErrExceedsDebtLimit = types.ErrExceedsDebtLimit
ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio
ErrCdpNotFound = types.ErrCdpNotFound
ErrDepositNotFound = types.ErrDepositNotFound
ErrInvalidDeposit = types.ErrInvalidDeposit
ErrInvalidPayment = types.ErrInvalidPayment
ErrDepositNotAvailable = types.ErrDepositNotAvailable
ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount
ErrCdpNotAvailable = types.ErrCdpNotAvailable
ErrBelowDebtFloor = types.ErrBelowDebtFloor
ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP
ErrInvalidDebtRequest = types.ErrInvalidDebtRequest
ErrDenomPrefixNotFound = types.ErrDenomPrefixNotFound
CdpIDKeyPrefix = types.CdpIDKeyPrefix CdpIDKeyPrefix = types.CdpIDKeyPrefix
CdpKeyPrefix = types.CdpKeyPrefix CdpKeyPrefix = types.CdpKeyPrefix
CollateralRatioIndexPrefix = types.CollateralRatioIndexPrefix CollateralRatioIndexPrefix = types.CollateralRatioIndexPrefix
@ -113,7 +114,7 @@ var (
PreviousDistributionTimeKey = types.PreviousDistributionTimeKey PreviousDistributionTimeKey = types.PreviousDistributionTimeKey
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
KeyCollateralParams = types.KeyCollateralParams KeyCollateralParams = types.KeyCollateralParams
KeyDebtParams = types.KeyDebtParams KeyDebtParam = types.KeyDebtParam
KeyDistributionFrequency = types.KeyDistributionFrequency KeyDistributionFrequency = types.KeyDistributionFrequency
KeyCircuitBreaker = types.KeyCircuitBreaker KeyCircuitBreaker = types.KeyCircuitBreaker
KeyDebtThreshold = types.KeyDebtThreshold KeyDebtThreshold = types.KeyDebtThreshold
@ -121,10 +122,11 @@ var (
DefaultGlobalDebt = types.DefaultGlobalDebt DefaultGlobalDebt = types.DefaultGlobalDebt
DefaultCircuitBreaker = types.DefaultCircuitBreaker DefaultCircuitBreaker = types.DefaultCircuitBreaker
DefaultCollateralParams = types.DefaultCollateralParams DefaultCollateralParams = types.DefaultCollateralParams
DefaultDebtParams = types.DefaultDebtParams DefaultDebtParam = types.DefaultDebtParam
DefaultCdpStartingID = types.DefaultCdpStartingID DefaultCdpStartingID = types.DefaultCdpStartingID
DefaultDebtDenom = types.DefaultDebtDenom DefaultDebtDenom = types.DefaultDebtDenom
DefaultGovDenom = types.DefaultGovDenom DefaultGovDenom = types.DefaultGovDenom
DefaultStableDenom = types.DefaultStableDenom
DefaultSurplusThreshold = types.DefaultSurplusThreshold DefaultSurplusThreshold = types.DefaultSurplusThreshold
DefaultDebtThreshold = types.DefaultDebtThreshold DefaultDebtThreshold = types.DefaultDebtThreshold
DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime

View File

@ -53,11 +53,11 @@ $ %s tx %s create 10000000uatom 1000usdx --from myKeyName
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
collateral, err := sdk.ParseCoins(args[0]) collateral, err := sdk.ParseCoin(args[0])
if err != nil { if err != nil {
return err return err
} }
debt, err := sdk.ParseCoins(args[1]) debt, err := sdk.ParseCoin(args[1])
if err != nil { if err != nil {
return err return err
} }
@ -88,7 +88,7 @@ $ %s tx %s deposit kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw 10000000uatom --f
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
collateral, err := sdk.ParseCoins(args[1]) collateral, err := sdk.ParseCoin(args[1])
if err != nil { if err != nil {
return err return err
} }
@ -123,7 +123,7 @@ $ %s tx %s withdraw kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw 10000000uatom --
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
collateral, err := sdk.ParseCoins(args[1]) collateral, err := sdk.ParseCoin(args[1])
if err != nil { if err != nil {
return err return err
} }
@ -158,7 +158,7 @@ $ %s tx %s draw uatom 1000usdx --from myKeyName
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
debt, err := sdk.ParseCoins(args[1]) debt, err := sdk.ParseCoin(args[1])
if err != nil { if err != nil {
return err return err
} }
@ -189,7 +189,7 @@ $ %s tx %s repay uatom 1000usdx --from myKeyName
cliCtx := context.NewCLIContext().WithCodec(cdc) cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
payment, err := sdk.ParseCoins(args[1]) payment, err := sdk.ParseCoin(args[1])
if err != nil { if err != nil {
return err return err
} }

View File

@ -18,8 +18,8 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
type PostCdpReq struct { type PostCdpReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` Collateral sdk.Coin `json:"collateral" yaml:"collateral"`
Principal sdk.Coins `json:"principal" yaml:"principal"` Principal sdk.Coin `json:"principal" yaml:"principal"`
} }
// PostDepositReq defines the properties of cdp request's body. // PostDepositReq defines the properties of cdp request's body.
@ -27,7 +27,7 @@ type PostDepositReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` Collateral sdk.Coin `json:"collateral" yaml:"collateral"`
} }
// PostWithdrawalReq defines the properties of cdp request's body. // PostWithdrawalReq defines the properties of cdp request's body.
@ -35,7 +35,7 @@ type PostWithdrawalReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` Collateral sdk.Coin `json:"collateral" yaml:"collateral"`
} }
// PostDrawReq defines the properties of cdp request's body. // PostDrawReq defines the properties of cdp request's body.
@ -43,7 +43,7 @@ type PostDrawReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Denom string `json:"denom" yaml:"denom"` Denom string `json:"denom" yaml:"denom"`
Principal sdk.Coins `json:"principal" yaml:"principal"` Principal sdk.Coin `json:"principal" yaml:"principal"`
} }
// PostRepayReq defines the properties of cdp request's body. // PostRepayReq defines the properties of cdp request's body.
@ -51,5 +51,5 @@ type PostRepayReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Denom string `json:"denom" yaml:"denom"` Denom string `json:"denom" yaml:"denom"`
Payment sdk.Coins `json:"payment" yaml:"payment"` Payment sdk.Coin `json:"payment" yaml:"payment"`
} }

View File

@ -46,9 +46,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, sk SupplyKeeper,
// set the per second fee rate for each collateral type // set the per second fee rate for each collateral type
for _, cp := range gs.Params.CollateralParams { for _, cp := range gs.Params.CollateralParams {
for _, dp := range gs.Params.DebtParams { k.SetTotalPrincipal(ctx, cp.Denom, gs.Params.DebtParam.Denom, sdk.ZeroInt())
k.SetTotalPrincipal(ctx, cp.Denom, dp.Denom, sdk.ZeroInt())
}
} }
// add cdps // add cdps
@ -56,11 +54,14 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, sk SupplyKeeper,
if cdp.ID == gs.StartingCdpID { if cdp.ID == gs.StartingCdpID {
panic(fmt.Sprintf("starting cdp id is assigned to an existing cdp: %s", cdp)) panic(fmt.Sprintf("starting cdp id is assigned to an existing cdp: %s", cdp))
} }
k.SetCDP(ctx, cdp) err := k.SetCDP(ctx, cdp)
if err != nil {
panic(fmt.Sprintf("error setting cdp: %v", err))
}
k.IndexCdpByOwner(ctx, cdp) k.IndexCdpByOwner(ctx, cdp)
ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.IndexCdpByCollateralRatio(ctx, cdp.Collateral[0].Denom, cdp.ID, ratio) k.IndexCdpByCollateralRatio(ctx, cdp.Collateral.Denom, cdp.ID, ratio)
k.IncrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, cdp.Principal) k.IncrementTotalPrincipal(ctx, cdp.Collateral.Denom, cdp.Principal)
} }
k.SetNextCdpID(ctx, gs.StartingCdpID) k.SetNextCdpID(ctx, gs.StartingCdpID)

View File

@ -1,12 +1,13 @@
package cdp_test package cdp_test
import ( import (
"strings"
"testing" "testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp" "github.com/kava-labs/kava/x/cdp"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
) )
@ -18,16 +19,86 @@ type GenesisTestSuite struct {
} }
func (suite *GenesisTestSuite) TestInvalidGenState() { func (suite *GenesisTestSuite) TestInvalidGenState() {
tApp := app.NewTestApp() type args struct {
for _, gs := range badGenStates() { params cdp.Params
cdps cdp.CDPs
appGS := app.GenesisState{"cdp": cdp.ModuleCdc.MustMarshalJSON(gs.Genesis)} deposits cdp.Deposits
suite.Panics(func() { startingID uint64
tApp.InitializeFromGenesisStates( debtDenom string
NewPricefeedGenStateMulti(), govDenom string
appGS, prevDistTime time.Time
) }
}, gs.Reason) type errArgs struct {
expectPass bool
contains string
}
type genesisTest struct {
name string
args args
errArgs errArgs
}
testCases := []struct {
name string
args args
errArgs errArgs
}{
{
name: "empty debt denom",
args: args{
params: cdp.DefaultParams(),
cdps: cdp.CDPs{},
deposits: cdp.Deposits{},
debtDenom: "",
govDenom: cdp.DefaultGovDenom,
prevDistTime: cdp.DefaultPreviousDistributionTime,
},
errArgs: errArgs{
expectPass: false,
contains: "debt denom invalid",
},
},
{
name: "empty gov denom",
args: args{
params: cdp.DefaultParams(),
cdps: cdp.CDPs{},
deposits: cdp.Deposits{},
debtDenom: cdp.DefaultDebtDenom,
govDenom: "",
prevDistTime: cdp.DefaultPreviousDistributionTime,
},
errArgs: errArgs{
expectPass: false,
contains: "gov denom invalid",
},
},
{
name: "empty distribution time",
args: args{
params: cdp.DefaultParams(),
cdps: cdp.CDPs{},
deposits: cdp.Deposits{},
debtDenom: cdp.DefaultDebtDenom,
govDenom: cdp.DefaultGovDenom,
prevDistTime: time.Time{},
},
errArgs: errArgs{
expectPass: false,
contains: "previous distribution time not set",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
gs := cdp.NewGenesisState(tc.args.params, tc.args.cdps, tc.args.deposits, tc.args.startingID, tc.args.debtDenom, tc.args.govDenom, tc.args.prevDistTime)
err := gs.Validate()
if tc.errArgs.expectPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
} }
} }

View File

@ -38,7 +38,7 @@ func handleMsgCreateCDP(ctx sdk.Context, k Keeper, msg MsgCreateCDP) (*sdk.Resul
sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()),
), ),
) )
id, _ := k.GetCdpID(ctx, msg.Sender, msg.Collateral[0].Denom) id, _ := k.GetCdpID(ctx, msg.Sender, msg.Collateral.Denom)
return &sdk.Result{ return &sdk.Result{
Data: GetCdpIDBytes(id), Data: GetCdpIDBytes(id),

View File

@ -43,8 +43,8 @@ func (suite *HandlerTestSuite) TestMsgCreateCdp() {
ak.SetAccount(suite.ctx, acc) ak.SetAccount(suite.ctx, acc)
msg := cdp.NewMsgCreateCDP( msg := cdp.NewMsgCreateCDP(
addrs[0], addrs[0],
cs(c("xrp", 200000000)), c("xrp", 200000000),
cs(c("usdx", 10000000)), c("usdx", 10000000),
) )
res, err := suite.handler(suite.ctx, msg) res, err := suite.handler(suite.ctx, msg)
suite.Require().NoError(err) suite.Require().NoError(err)

View File

@ -39,7 +39,7 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { 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.NewInt64Coin("usdx", 1000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold, DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
@ -47,7 +47,7 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
{ {
Denom: asset, Denom: asset,
LiquidationRatio: liquidationRatio, LiquidationRatio: liquidationRatio,
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
LiquidationPenalty: d("0.05"), LiquidationPenalty: d("0.05"),
AuctionSize: i(1000000000), AuctionSize: i(1000000000),
@ -56,14 +56,12 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
MarketID: asset + ":usd", MarketID: asset + ":usd",
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParam: cdp.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: i(6),
ConversionFactor: i(6), DebtFloor: i(10000000),
DebtFloor: i(10000000), SavingsRate: d("0.95"),
SavingsRate: d("0.95"),
},
}, },
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
@ -103,7 +101,7 @@ func NewPricefeedGenStateMulti() app.GenesisState {
func NewCDPGenStateMulti() app.GenesisState { 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.NewInt64Coin("usdx", 1000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold, DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
@ -111,7 +109,7 @@ func NewCDPGenStateMulti() app.GenesisState {
{ {
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.NewInt64Coin("usdx", 500000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
LiquidationPenalty: d("0.05"), LiquidationPenalty: d("0.05"),
AuctionSize: i(7000000000), AuctionSize: i(7000000000),
@ -122,7 +120,7 @@ func NewCDPGenStateMulti() app.GenesisState {
{ {
Denom: "btc", Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
LiquidationPenalty: d("0.025"), LiquidationPenalty: d("0.025"),
AuctionSize: i(10000000), AuctionSize: i(10000000),
@ -131,21 +129,12 @@ func NewCDPGenStateMulti() app.GenesisState {
ConversionFactor: i(8), ConversionFactor: i(8),
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParam: cdp.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: i(6),
ConversionFactor: i(6), DebtFloor: i(10000000),
DebtFloor: i(10000000), SavingsRate: d("0.95"),
SavingsRate: d("0.95"),
},
{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
}, },
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
@ -159,121 +148,10 @@ func NewCDPGenStateMulti() app.GenesisState {
func cdps() (cdps cdp.CDPs) { func cdps() (cdps cdp.CDPs) {
_, addrs := app.GeneratePrivKeyAddressPairs(3) _, addrs := app.GeneratePrivKeyAddressPairs(3)
c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(100000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(8000000))), tmtime.Canonical(time.Now())) c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoin("xrp", sdk.NewInt(100000000)), sdk.NewCoin("usdx", sdk.NewInt(8000000)), tmtime.Canonical(time.Now()))
c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(100000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10000000))), tmtime.Canonical(time.Now())) c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoin("xrp", sdk.NewInt(100000000)), sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoins(sdk.NewCoin("btc", sdk.NewInt(1000000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10000000))), tmtime.Canonical(time.Now())) c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoin("btc", sdk.NewInt(1000000000)), sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(1000000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(50000000))), tmtime.Canonical(time.Now())) c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoin("xrp", sdk.NewInt(1000000000)), sdk.NewCoin("usdx", sdk.NewInt(50000000)), tmtime.Canonical(time.Now()))
cdps = append(cdps, c1, c2, c3, c4) cdps = append(cdps, c1, c2, c3, c4)
return return
} }
type badGenState struct {
Genesis cdp.GenesisState
Reason string
}
func badGenStates() []badGenState {
g1 := baseGenState()
g1.Params.CollateralParams[0].Denom = "btc"
g2 := baseGenState()
g2.Params.CollateralParams[0].Prefix = 0x21
g3 := baseGenState()
g3.Params.CollateralParams[0].DebtLimit = sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("lol", 500000000000))
g4 := baseGenState()
g4.Params.CollateralParams[0].DebtLimit = sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000001))
g5 := baseGenState()
g5.Params.CollateralParams[0].DebtLimit = sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000001), sdk.NewInt64Coin("susd", 500000000000))
g6 := baseGenState()
g6.Params.DebtParams[0].Denom = "susd"
g8 := baseGenState()
g8.Params.DebtParams = append(g8.Params.DebtParams, cdp.DebtParam{
Denom: "lol",
ReferenceAsset: "usd",
})
g9 := baseGenState()
g9.DebtDenom = ""
g10 := baseGenState()
g10.Params.CollateralParams[0].AuctionSize = i(-10)
g11 := baseGenState()
g11.Params.CollateralParams[0].LiquidationPenalty = d("5.0")
g12 := baseGenState()
g12.GovDenom = ""
g13 := baseGenState()
g13.Params.DebtParams[0].SavingsRate = d("4.0")
return []badGenState{
badGenState{Genesis: g1, Reason: "duplicate collateral denom"},
badGenState{Genesis: g2, Reason: "duplicate collateral prefix"},
badGenState{Genesis: g3, Reason: "invalid debt limit"},
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: g8, Reason: "debt param not found in global debt limit"},
badGenState{Genesis: g9, Reason: "debt denom not set"},
badGenState{Genesis: g10, Reason: "negative auction size"},
badGenState{Genesis: g11, Reason: "invalid liquidation penalty"},
badGenState{Genesis: g12, Reason: "gov denom not set"},
badGenState{Genesis: g13, Reason: "invalid savings rate"},
}
}
func baseGenState() cdp.GenesisState {
return cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
CollateralParams: cdp.CollateralParams{
{
Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
Prefix: 0x20,
MarketID: "xrp:usd",
ConversionFactor: i(6),
},
{
Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
Prefix: 0x21,
MarketID: "btc:usd",
ConversionFactor: i(8),
},
},
DebtParams: cdp.DebtParams{
{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
},
{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
},
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
}

View File

@ -12,11 +12,11 @@ const (
type partialDeposit struct { type partialDeposit struct {
Depositor sdk.AccAddress Depositor sdk.AccAddress
Amount sdk.Coins Amount sdk.Coin
DebtShare sdk.Int DebtShare sdk.Int
} }
func newPartialDeposit(depositor sdk.AccAddress, amount sdk.Coins, ds sdk.Int) partialDeposit { func newPartialDeposit(depositor sdk.AccAddress, amount sdk.Coin, ds sdk.Int) partialDeposit {
return partialDeposit{ return partialDeposit{
Depositor: depositor, Depositor: depositor,
Amount: amount, Amount: amount,
@ -29,7 +29,7 @@ type partialDeposits []partialDeposit
func (pd partialDeposits) SumCollateral() (sum sdk.Int) { func (pd partialDeposits) SumCollateral() (sum sdk.Int) {
sum = sdk.ZeroInt() sum = sdk.ZeroInt()
for _, d := range pd { for _, d := range pd {
sum = sum.Add(d.Amount[0].Amount) sum = sum.Add(d.Amount.Amount)
} }
return return
} }
@ -44,7 +44,7 @@ func (pd partialDeposits) SumDebt() (sum sdk.Int) {
// AuctionCollateral creates auctions from the input deposits which attempt to raise the corresponding amount of debt // 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) error { func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) error {
auctionSize := k.getAuctionSize(ctx, deposits[0].Amount[0].Denom) auctionSize := k.getAuctionSize(ctx, deposits[0].Amount.Denom)
partialAuctionDeposits := partialDeposits{} partialAuctionDeposits := partialDeposits{}
totalCollateral := deposits.SumCollateral() totalCollateral := deposits.SumCollateral()
for totalCollateral.GT(sdk.ZeroInt()) { for totalCollateral.GT(sdk.ZeroInt()) {
@ -52,8 +52,8 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
if dep.Amount.IsZero() { if dep.Amount.IsZero() {
continue continue
} }
collateralAmount := dep.Amount[0].Amount collateralAmount := dep.Amount.Amount
collateralDenom := dep.Amount[0].Denom collateralDenom := dep.Amount.Denom
// create auctions from individual deposits that are larger than the auction size // create auctions from individual deposits that are larger than the auction size
debtChange, collateralChange, err := k.CreateAuctionsFromDeposit(ctx, dep, debt, totalCollateral, auctionSize, bidDenom) debtChange, collateralChange, err := k.CreateAuctionsFromDeposit(ctx, dep, debt, totalCollateral, auctionSize, bidDenom)
if err != nil { if err != nil {
@ -61,7 +61,7 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
} }
debt = debt.Sub(debtChange) debt = debt.Sub(debtChange)
totalCollateral = totalCollateral.Sub(collateralChange) totalCollateral = totalCollateral.Sub(collateralChange)
dep.Amount = sdk.NewCoins(sdk.NewCoin(collateralDenom, collateralAmount.Sub(collateralChange))) dep.Amount = sdk.NewCoin(collateralDenom, collateralAmount.Sub(collateralChange))
collateralAmount = collateralAmount.Sub(collateralChange) collateralAmount = collateralAmount.Sub(collateralChange)
// if there is leftover collateral that is less than a lot // if there is leftover collateral that is less than a lot
if !dep.Amount.IsZero() { if !dep.Amount.IsZero() {
@ -73,11 +73,11 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
// append the deposit to the partial deposits and zero out the deposit // append the deposit to the partial deposits and zero out the deposit
pd := newPartialDeposit(dep.Depositor, dep.Amount, debtCoveredByDeposit) pd := newPartialDeposit(dep.Depositor, dep.Amount, debtCoveredByDeposit)
partialAuctionDeposits = append(partialAuctionDeposits, pd) partialAuctionDeposits = append(partialAuctionDeposits, pd)
dep.Amount = sdk.NewCoins(sdk.NewCoin(collateralDenom, sdk.ZeroInt())) dep.Amount = sdk.NewCoin(collateralDenom, sdk.ZeroInt())
} else { } else {
// if the sum of partial deposits now makes a lot // if the sum of partial deposits now makes a lot
partialCollateral := sdk.NewCoins(sdk.NewCoin(collateralDenom, auctionSize.Sub(partialAuctionDeposits.SumCollateral()))) partialCollateral := sdk.NewCoin(collateralDenom, auctionSize.Sub(partialAuctionDeposits.SumCollateral()))
partialAmount := partialCollateral[0].Amount partialAmount := partialCollateral.Amount
partialDebt := (partialAmount.Quo(collateralAmount)).Mul(debtCoveredByDeposit) partialDebt := (partialAmount.Quo(collateralAmount)).Mul(debtCoveredByDeposit)
// create a partial deposit from the deposit // create a partial deposit from the deposit
@ -93,7 +93,7 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
totalCollateral = totalCollateral.Sub(collateralChange) totalCollateral = totalCollateral.Sub(collateralChange)
// reset partial deposits and update the deposit amount // reset partial deposits and update the deposit amount
partialAuctionDeposits = partialDeposits{} partialAuctionDeposits = partialDeposits{}
dep.Amount = sdk.NewCoins(sdk.NewCoin(collateralDenom, collateralAmount.Sub(partialAmount))) dep.Amount = sdk.NewCoin(collateralDenom, collateralAmount.Sub(partialAmount))
} }
} }
deposits[i] = dep deposits[i] = dep
@ -110,11 +110,13 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt
} }
// CreateAuctionsFromDeposit creates auctions from the input deposit until there is less than auctionSize left on the deposit // 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 error) { 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 error) {
debtChange = sdk.ZeroInt() debtChange = sdk.ZeroInt()
collateralChange = sdk.ZeroInt() collateralChange = sdk.ZeroInt()
depositAmount := dep.Amount[0].Amount depositAmount := dep.Amount.Amount
depositDenom := dep.Amount[0].Denom depositDenom := dep.Amount.Denom
for depositAmount.GTE(auctionSize) { for depositAmount.GTE(auctionSize) {
// figure out how much debt is covered by one lots worth of collateral // 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() depositDebtAmount := (sdk.NewDecFromInt(auctionSize).Quo(sdk.NewDecFromInt(totalCollateral))).Mul(sdk.NewDecFromInt(debt)).RoundInt()
@ -142,13 +144,13 @@ func (k Keeper) CreateAuctionFromPartialDeposits(ctx sdk.Context, partialDeps pa
returnAddrs := []sdk.AccAddress{} returnAddrs := []sdk.AccAddress{}
returnWeights := []sdk.Int{} returnWeights := []sdk.Int{}
depositDenom := partialDeps[0].Amount[0].Denom depositDenom := partialDeps[0].Amount.Denom
for _, pd := range partialDeps { for _, pd := range partialDeps {
returnAddrs = append(returnAddrs, pd.Depositor) returnAddrs = append(returnAddrs, pd.Depositor)
returnWeights = append(returnWeights, pd.DebtShare) returnWeights = append(returnWeights, pd.DebtShare)
} }
penalty := k.ApplyLiquidationPenalty(ctx, depositDenom, partialDeps.SumDebt()) penalty := k.ApplyLiquidationPenalty(ctx, depositDenom, partialDeps.SumDebt())
_, err = k.auctionKeeper.StartCollateralAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(partialDeps[0].Amount[0].Denom, auctionSize), sdk.NewCoin(bidDenom, partialDeps.SumDebt().Add(penalty)), returnAddrs, returnWeights, sdk.NewCoin(k.GetDebtDenom(ctx), partialDeps.SumDebt())) _, err = k.auctionKeeper.StartCollateralAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(partialDeps[0].Amount.Denom, auctionSize), sdk.NewCoin(bidDenom, partialDeps.SumDebt().Add(penalty)), returnAddrs, returnWeights, sdk.NewCoin(k.GetDebtDenom(ctx), partialDeps.SumDebt()))
if err != nil { if err != nil {
return sdk.ZeroInt(), sdk.ZeroInt(), err return sdk.ZeroInt(), sdk.ZeroInt(), err
} }
@ -166,27 +168,24 @@ func (k Keeper) NetSurplusAndDebt(ctx sdk.Context) error {
if netAmount.IsZero() { if netAmount.IsZero() {
return nil return nil
} }
// burn debt coins equal to netAmount
err := k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), netAmount))) err := k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), netAmount)))
if err != nil { if err != nil {
return err return err
} }
for netAmount.GT(sdk.ZeroInt()) { // burn stable coins equal to netAmount
for _, dp := range k.GetParams(ctx).DebtParams { dp := k.GetParams(ctx).DebtParam
balance := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom) balance := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
if balance.LT(netAmount) { if balance.LT(netAmount) {
err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, balance))) err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, balance)))
if err != nil { if err != nil {
return err 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
}
err = k.supplyKeeper.BurnCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, netAmount)))
if err != nil {
return err
} }
return nil return nil
} }
@ -195,10 +194,9 @@ func (k Keeper) NetSurplusAndDebt(ctx sdk.Context) error {
func (k Keeper) GetTotalSurplus(ctx sdk.Context, accountName string) sdk.Int { func (k Keeper) GetTotalSurplus(ctx sdk.Context, accountName string) sdk.Int {
acc := k.supplyKeeper.GetModuleAccount(ctx, accountName) acc := k.supplyKeeper.GetModuleAccount(ctx, accountName)
totalSurplus := sdk.ZeroInt() totalSurplus := sdk.ZeroInt()
for _, dp := range k.GetParams(ctx).DebtParams { dp := k.GetParams(ctx).DebtParam
surplus := acc.GetCoins().AmountOf(dp.Denom) surplus := acc.GetCoins().AmountOf(dp.Denom)
totalSurplus = totalSurplus.Add(surplus) totalSurplus = totalSurplus.Add(surplus)
}
return totalSurplus return totalSurplus
} }
@ -221,14 +219,12 @@ func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) error {
} }
} }
for _, dp := range params.DebtParams { surplus := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(params.DebtParam.Denom)
surplus := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom) if surplus.GTE(params.SurplusAuctionThreshold) {
if surplus.GTE(params.SurplusAuctionThreshold) { surplusLot := sdk.NewCoin(params.DebtParam.Denom, surplus)
surplusLot := sdk.NewCoin(dp.Denom, surplus) _, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx))
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx)) if err != nil {
if err != nil { return err
return err
}
} }
} }
return nil return nil

View File

@ -14,26 +14,26 @@ import (
const BaseDigitFactor = 1000000000000000000 const BaseDigitFactor = 1000000000000000000
// AddCdp adds a cdp for a specific owner and collateral type // AddCdp adds a cdp for a specific owner and collateral type
func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coins, principal sdk.Coins) error { func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coin, principal sdk.Coin) error {
// validation // validation
err := k.ValidateCollateral(ctx, collateral) err := k.ValidateCollateral(ctx, collateral)
if err != nil { if err != nil {
return err return err
} }
_, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral[0].Denom) _, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral.Denom)
if found { if found {
return sdkerrors.Wrapf(types.ErrCdpAlreadyExists, "owner %s, denom %s", owner, collateral[0].Denom) return sdkerrors.Wrapf(types.ErrCdpAlreadyExists, "owner %s, denom %s", owner, collateral.Denom)
} }
err = k.ValidatePrincipalAdd(ctx, principal) err = k.ValidatePrincipalAdd(ctx, principal)
if err != nil { if err != nil {
return err return err
} }
err = k.ValidateDebtLimit(ctx, collateral[0].Denom, principal) err = k.ValidateDebtLimit(ctx, collateral.Denom, principal)
if err != nil { if err != nil {
return err return err
} }
err = k.ValidateCollateralizationRatio(ctx, collateral, principal, sdk.NewCoins()) err = k.ValidateCollateralizationRatio(ctx, collateral, principal, sdk.NewCoin(principal.Denom, sdk.ZeroInt()))
if err != nil { if err != nil {
return err return err
} }
@ -42,17 +42,17 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
id := k.GetNextCdpID(ctx) id := k.GetNextCdpID(ctx)
cdp := types.NewCDP(id, owner, collateral, principal, ctx.BlockHeader().Time) cdp := types.NewCDP(id, owner, collateral, principal, ctx.BlockHeader().Time)
deposit := types.NewDeposit(cdp.ID, owner, collateral) deposit := types.NewDeposit(cdp.ID, owner, collateral)
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, collateral) err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, sdk.NewCoins(collateral))
if err != nil { if err != nil {
return err return err
} }
// mint the principal and send to the owners account // mint the principal and send to the owners account
err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, principal) err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(principal))
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, owner, principal) err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, owner, sdk.NewCoins(principal))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -86,11 +86,14 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
) )
// update total principal for input collateral type // update total principal for input collateral type
k.IncrementTotalPrincipal(ctx, collateral[0].Denom, principal) k.IncrementTotalPrincipal(ctx, collateral.Denom, principal)
// set the cdp, deposit, and indexes in the store // set the cdp, deposit, and indexes in the store
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, collateral, principal) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, collateral, principal)
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
if err != nil {
return err
}
k.IndexCdpByOwner(ctx, cdp) k.IndexCdpByOwner(ctx, cdp)
k.SetDeposit(ctx, deposit) k.SetDeposit(ctx, deposit)
k.SetNextCdpID(ctx, id+1) k.SetNextCdpID(ctx, id+1)
@ -98,18 +101,19 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi
} }
// SetCdpAndCollateralRatioIndex sets the cdp and collateral ratio index in the store // SetCdpAndCollateralRatioIndex sets the cdp and collateral ratio index in the store
func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ratio sdk.Dec) { func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ratio sdk.Dec) error {
k.SetCDP(ctx, cdp) err := k.SetCDP(ctx, cdp)
k.IndexCdpByCollateralRatio(ctx, cdp.Collateral[0].Denom, cdp.ID, ratio) if err != nil {
return err
}
k.IndexCdpByCollateralRatio(ctx, cdp.Collateral.Denom, cdp.ID, ratio)
return nil
} }
// MintDebtCoins mints debt coins in the cdp module account // MintDebtCoins mints debt coins in the cdp module account
func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coins) error { func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coin) error {
coinsToMint := sdk.NewCoins() debtCoins := sdk.NewCoins(sdk.NewCoin(denom, principalCoins.Amount))
for _, sc := range principalCoins { err := k.supplyKeeper.MintCoins(ctx, moduleAccount, debtCoins)
coinsToMint = coinsToMint.Add(sdk.NewCoin(denom, sc.Amount))
}
err := k.supplyKeeper.MintCoins(ctx, moduleAccount, coinsToMint)
if err != nil { if err != nil {
return err return err
} }
@ -117,12 +121,9 @@ func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom strin
} }
// BurnDebtCoins burns debt coins from the cdp module account // BurnDebtCoins burns debt coins from the cdp module account
func (k Keeper) BurnDebtCoins(ctx sdk.Context, moduleAccount string, denom string, paymentCoins sdk.Coins) error { func (k Keeper) BurnDebtCoins(ctx sdk.Context, moduleAccount string, denom string, paymentCoins sdk.Coin) error {
coinsToBurn := sdk.NewCoins() debtCoins := sdk.NewCoins(sdk.NewCoin(denom, paymentCoins.Amount))
for _, pc := range paymentCoins { err := k.supplyKeeper.BurnCoins(ctx, moduleAccount, debtCoins)
coinsToBurn = coinsToBurn.Add(sdk.NewCoin(denom, pc.Amount))
}
err := k.supplyKeeper.BurnCoins(ctx, moduleAccount, coinsToBurn)
if err != nil { if err != nil {
return err return err
} }
@ -178,7 +179,10 @@ func (k Keeper) GetCdpByOwnerAndDenom(ctx sdk.Context, owner sdk.AccAddress, den
func (k Keeper) GetCDP(ctx sdk.Context, collateralDenom string, cdpID uint64) (types.CDP, bool) { func (k Keeper) GetCDP(ctx sdk.Context, collateralDenom string, cdpID uint64) (types.CDP, bool) {
// get store // get store
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
db, _ := k.GetDenomPrefix(ctx, collateralDenom) db, found := k.GetDenomPrefix(ctx, collateralDenom)
if !found {
return types.CDP{}, false
}
// get CDP // get CDP
bz := store.Get(types.CdpKey(db, cdpID)) bz := store.Get(types.CdpKey(db, cdpID))
// unmarshal // unmarshal
@ -191,19 +195,26 @@ func (k Keeper) GetCDP(ctx sdk.Context, collateralDenom string, cdpID uint64) (t
} }
// SetCDP sets a cdp in the store // SetCDP sets a cdp in the store
func (k Keeper) SetCDP(ctx sdk.Context, cdp types.CDP) { func (k Keeper) SetCDP(ctx sdk.Context, cdp types.CDP) error {
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
db, _ := k.GetDenomPrefix(ctx, cdp.Collateral[0].Denom) db, found := k.GetDenomPrefix(ctx, cdp.Collateral.Denom)
if !found {
sdkerrors.Wrapf(types.ErrDenomPrefixNotFound, "%s", cdp.Collateral.Denom)
}
bz := k.cdc.MustMarshalBinaryLengthPrefixed(cdp) bz := k.cdc.MustMarshalBinaryLengthPrefixed(cdp)
store.Set(types.CdpKey(db, cdp.ID), bz) store.Set(types.CdpKey(db, cdp.ID), bz)
return return nil
} }
// DeleteCDP deletes a cdp from the store // DeleteCDP deletes a cdp from the store
func (k Keeper) DeleteCDP(ctx sdk.Context, cdp types.CDP) { func (k Keeper) DeleteCDP(ctx sdk.Context, cdp types.CDP) error {
store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix) store := prefix.NewStore(ctx.KVStore(k.key), types.CdpKeyPrefix)
db, _ := k.GetDenomPrefix(ctx, cdp.Collateral[0].Denom) db, found := k.GetDenomPrefix(ctx, cdp.Collateral.Denom)
if !found {
sdkerrors.Wrapf(types.ErrDenomPrefixNotFound, "%s", cdp.Collateral.Denom)
}
store.Delete(types.CdpKey(db, cdp.ID)) store.Delete(types.CdpKey(db, cdp.ID))
return nil
} }
@ -341,118 +352,99 @@ func (k Keeper) SetGovDenom(ctx sdk.Context, denom string) {
} }
// 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) error { func (k Keeper) ValidateCollateral(ctx sdk.Context, collateral sdk.Coin) error {
if len(collateral) != 1 { _, found := k.GetCollateral(ctx, collateral.Denom)
return sdkerrors.Wrapf(types.ErrInvalidCollateralLength, "%d", len(collateral))
}
_, found := k.GetCollateral(ctx, collateral[0].Denom)
if !found { if !found {
return sdkerrors.Wrap(types.ErrCollateralNotSupported, collateral[0].Denom) return sdkerrors.Wrap(types.ErrCollateralNotSupported, collateral.Denom)
} }
return nil return nil
} }
// ValidatePrincipalAdd validates that an asset is valid for use as debt when creating a new cdp // ValidatePrincipalAdd validates that an asset is valid for use as debt when creating a new cdp
func (k Keeper) ValidatePrincipalAdd(ctx sdk.Context, principal sdk.Coins) error { func (k Keeper) ValidatePrincipalAdd(ctx sdk.Context, principal sdk.Coin) error {
for _, dc := range principal { dp, found := k.GetDebtParam(ctx, principal.Denom)
dp, found := k.GetDebtParam(ctx, dc.Denom) if !found {
if !found { return sdkerrors.Wrap(types.ErrDebtNotSupported, principal.Denom)
return sdkerrors.Wrap(types.ErrDebtNotSupported, dc.Denom) }
} if principal.Amount.LT(dp.DebtFloor) {
if dc.Amount.LT(dp.DebtFloor) { return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", principal, dp.DebtFloor)
return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", dc, dp.DebtFloor)
}
} }
return nil return nil
} }
// ValidatePrincipalDraw validates that an asset is valid for use as debt when drawing debt off an existing cdp // 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) error { func (k Keeper) ValidatePrincipalDraw(ctx sdk.Context, principal sdk.Coin, expectedDenom string) error {
for _, dc := range principal { if principal.Denom != expectedDenom {
_, found := k.GetDebtParam(ctx, dc.Denom) return sdkerrors.Wrapf(types.ErrInvalidDebtRequest, "proposed %s, expected %s", principal.Denom, expectedDenom)
if !found { }
return sdkerrors.Wrap(types.ErrDebtNotSupported, dc.Denom) _, found := k.GetDebtParam(ctx, principal.Denom)
} if !found {
return sdkerrors.Wrap(types.ErrDebtNotSupported, principal.Denom)
} }
return nil return nil
} }
// ValidateDebtLimit validates that the input debt amount does not exceed the global debt limit or the debt limit for that collateral // ValidateDebtLimit validates that the input debt amount does not exceed the global debt limit or the debt limit for that collateral
func (k Keeper) ValidateDebtLimit(ctx sdk.Context, collateralDenom string, principal sdk.Coins) error { func (k Keeper) ValidateDebtLimit(ctx sdk.Context, collateralDenom string, principal sdk.Coin) error {
cp, found := k.GetCollateral(ctx, collateralDenom) cp, found := k.GetCollateral(ctx, collateralDenom)
if !found { if !found {
return sdkerrors.Wrap(types.ErrCollateralNotSupported, collateralDenom) return sdkerrors.Wrap(types.ErrCollateralNotSupported, collateralDenom)
} }
totalPrincipal := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom).Add(principal.Amount)
for _, dc := range principal { collateralLimit := cp.DebtLimit.Amount
totalPrincipal := k.GetTotalPrincipal(ctx, collateralDenom, dc.Denom).Add(dc.Amount) if totalPrincipal.GT(collateralLimit) {
collateralLimit := cp.DebtLimit.AmountOf(dc.Denom) return sdkerrors.Wrapf(types.ErrExceedsDebtLimit, "debt increase %s > collateral debt limit %s", sdk.NewCoins(sdk.NewCoin(principal.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(principal.Denom, collateralLimit)))
if totalPrincipal.GT(collateralLimit) { }
return sdkerrors.Wrapf(types.ErrExceedsDebtLimit, "debt increase %s > collateral debt limit %s", sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, collateralLimit))) globalLimit := k.GetParams(ctx).GlobalDebtLimit.Amount
} if totalPrincipal.GT(globalLimit) {
globalLimit := k.GetParams(ctx).GlobalDebtLimit.AmountOf(dc.Denom) return sdkerrors.Wrapf(types.ErrExceedsDebtLimit, "debt increase %s > global debt limit %s", sdk.NewCoin(principal.Denom, totalPrincipal), sdk.NewCoin(principal.Denom, globalLimit))
if totalPrincipal.GT(globalLimit) {
return sdkerrors.Wrapf(types.ErrExceedsDebtLimit, "debt increase %s > global debt limit %s", sdk.NewCoin(dc.Denom, totalPrincipal), sdk.NewCoin(dc.Denom, globalLimit))
}
} }
return nil return nil
} }
// ValidateCollateralizationRatio validate that adding the input principal doesn't put the cdp below the liquidation ratio // ValidateCollateralizationRatio validate that adding the input principal doesn't put the cdp below the liquidation ratio
func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coins, principal sdk.Coins, fees sdk.Coins) error { func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coin, principal sdk.Coin, fees sdk.Coin) error {
// //
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, collateral, principal, fees) collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, collateral, principal, fees)
if err != nil { if err != nil {
return err return err
} }
liquidationRatio := k.getLiquidationRatio(ctx, collateral[0].Denom) liquidationRatio := k.getLiquidationRatio(ctx, collateral.Denom)
if collateralizationRatio.LT(liquidationRatio) { if collateralizationRatio.LT(liquidationRatio) {
return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ratio %s", collateral[0].Denom, collateralizationRatio, liquidationRatio) return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ratio %s", collateral.Denom, collateralizationRatio, liquidationRatio)
} }
return nil return nil
} }
// CalculateCollateralToDebtRatio returns the collateral to debt ratio of the input collateral and debt amounts // CalculateCollateralToDebtRatio returns the collateral to debt ratio of the input collateral and debt amounts
func (k Keeper) CalculateCollateralToDebtRatio(ctx sdk.Context, collateral sdk.Coins, debt sdk.Coins) sdk.Dec { func (k Keeper) CalculateCollateralToDebtRatio(ctx sdk.Context, collateral sdk.Coin, debt sdk.Coin) sdk.Dec {
debtTotal := sdk.ZeroDec() debtTotal := k.convertDebtToBaseUnits(ctx, debt)
for _, dc := range debt {
debtBaseUnits := k.convertDebtToBaseUnits(ctx, dc)
debtTotal = debtTotal.Add(debtBaseUnits)
}
if debtTotal.IsZero() || debtTotal.GTE(types.MaxSortableDec) { if debtTotal.IsZero() || debtTotal.GTE(types.MaxSortableDec) {
return types.MaxSortableDec.Sub(sdk.SmallestDec()) return types.MaxSortableDec.Sub(sdk.SmallestDec())
} }
collateralBaseUnits := k.convertCollateralToBaseUnits(ctx, collateral[0]) collateralBaseUnits := k.convertCollateralToBaseUnits(ctx, collateral)
return collateralBaseUnits.Quo(debtTotal) return collateralBaseUnits.Quo(debtTotal)
} }
// LoadAugmentedCDP creates a new augmented CDP from an existing CDP // LoadAugmentedCDP creates a new augmented CDP from an existing CDP
func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.AugmentedCDP, error) { func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.AugmentedCDP, error) {
// calculate additional fees
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)
totalFees := cdp.AccumulatedFees.Add(fees...)
// calculate collateralization ratio // calculate collateralization ratio
collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal, totalFees) collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal, cdp.AccumulatedFees)
if err != nil { if err != nil {
return types.AugmentedCDP{}, err return types.AugmentedCDP{}, err
} }
// total debt is the sum of all oustanding principal and fees // total debt is the sum of all oustanding principal and fees
var totalDebt int64 var totalDebt int64
for _, principalCoin := range cdp.Principal { totalDebt += cdp.Principal.Amount.Int64()
totalDebt += principalCoin.Amount.Int64() totalDebt += cdp.AccumulatedFees.Amount.Int64()
}
for _, feeCoin := range cdp.AccumulatedFees.Add(fees...) {
totalDebt += feeCoin.Amount.Int64()
}
// convert collateral value to debt coin // convert collateral value to debt coin
debtBaseAdjusted := sdk.NewDec(totalDebt).QuoInt64(BaseDigitFactor) debtBaseAdjusted := sdk.NewDec(totalDebt).QuoInt64(BaseDigitFactor)
collateralValueInDebtDenom := collateralizationRatio.Mul(debtBaseAdjusted) collateralValueInDebtDenom := collateralizationRatio.Mul(debtBaseAdjusted)
collateralValueInDebt := sdk.NewInt64Coin(cdp.Principal[0].Denom, collateralValueInDebtDenom.Int64()) collateralValueInDebt := sdk.NewInt64Coin(cdp.Principal.Denom, collateralValueInDebtDenom.Int64())
// create new augmuented cdp // create new augmuented cdp
augmentedCDP := types.NewAugmentedCDP(cdp, collateralValueInDebt, collateralizationRatio) augmentedCDP := types.NewAugmentedCDP(cdp, collateralValueInDebt, collateralizationRatio)
@ -460,27 +452,23 @@ func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.Augmente
} }
// CalculateCollateralizationRatio returns the collateralization ratio of the input collateral to the input debt plus fees // CalculateCollateralizationRatio returns the collateralization ratio of the input collateral to the input debt plus fees
func (k Keeper) CalculateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coins, principal sdk.Coins, fees sdk.Coins) (sdk.Dec, error) { func (k Keeper) CalculateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coin, principal sdk.Coin, fees sdk.Coin) (sdk.Dec, error) {
if collateral.IsZero() { if collateral.IsZero() {
return sdk.ZeroDec(), nil return sdk.ZeroDec(), nil
} }
marketID := k.getMarketID(ctx, collateral[0].Denom) marketID := k.getMarketID(ctx, collateral.Denom)
price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID)
if err != nil { if err != nil {
return sdk.Dec{}, err return sdk.Dec{}, err
} }
collateralBaseUnits := k.convertCollateralToBaseUnits(ctx, collateral[0]) collateralBaseUnits := k.convertCollateralToBaseUnits(ctx, collateral)
collateralValue := collateralBaseUnits.Mul(price.Price) collateralValue := collateralBaseUnits.Mul(price.Price)
principalTotal := sdk.ZeroDec() prinicpalBaseUnits := k.convertDebtToBaseUnits(ctx, principal)
for _, pc := range principal { principalTotal := prinicpalBaseUnits
prinicpalBaseUnits := k.convertDebtToBaseUnits(ctx, pc) feeBaseUnits := k.convertDebtToBaseUnits(ctx, fees)
principalTotal = principalTotal.Add(prinicpalBaseUnits) principalTotal = principalTotal.Add(feeBaseUnits)
}
for _, fc := range fees {
feeBaseUnits := k.convertDebtToBaseUnits(ctx, fc)
principalTotal = principalTotal.Add(feeBaseUnits)
}
collateralRatio := collateralValue.Quo(principalTotal) collateralRatio := collateralValue.Quo(principalTotal)
return collateralRatio, nil return collateralRatio, nil
} }

View File

@ -44,27 +44,29 @@ func (suite *CdpTestSuite) TestAddCdp() {
acc := ak.NewAccountWithAddress(suite.ctx, addrs[0]) acc := ak.NewAccountWithAddress(suite.ctx, addrs[0])
acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000))) acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
ak.SetAccount(suite.ctx, acc) ak.SetAccount(suite.ctx, acc)
err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("usdx", 26000000))) err := suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 200000000), c("usdx", 26000000))
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio))
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 500000000)), cs(c("usdx", 26000000))) err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 500000000), c("usdx", 26000000))
suite.Error(err) // insufficient balance suite.Error(err) // insufficient balance
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("xusd", 10000000))) err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 200000000), c("xusd", 10000000))
suite.Require().True(errors.Is(err, types.ErrDebtNotSupported)) suite.Require().True(errors.Is(err, types.ErrDebtNotSupported))
acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1]) acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1])
acc2.SetCoins(cs(c("btc", 500000000000))) acc2.SetCoins(cs(c("btc", 500000000000)))
ak.SetAccount(suite.ctx, acc2) ak.SetAccount(suite.ctx, acc2)
err = suite.keeper.AddCdp(suite.ctx, addrs[1], cs(c("btc", 500000000000)), cs(c("usdx", 500000000001))) err = suite.keeper.AddCdp(suite.ctx, addrs[1], c("btc", 500000000000), c("usdx", 500000000001))
suite.Require().True(errors.Is(err, types.ErrExceedsDebtLimit)) suite.Require().True(errors.Is(err, types.ErrExceedsDebtLimit))
ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2)) ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2))
pk := suite.app.GetPriceFeedKeeper() pk := suite.app.GetPriceFeedKeeper()
_ = pk.SetCurrentPrices(ctx, "xrp:usd") err = pk.SetCurrentPrices(ctx, "xrp:usd")
err = suite.keeper.AddCdp(ctx, addrs[0], cs(c("xrp", 100000000)), cs(c("usdx", 10000000))) suite.Error(err)
err = suite.keeper.AddCdp(ctx, addrs[0], c("xrp", 100000000), c("usdx", 10000000))
suite.Error(err) // no prices in pricefeed suite.Error(err) // no prices in pricefeed
_ = pk.SetCurrentPrices(suite.ctx, "xrp:usd") err = pk.SetCurrentPrices(suite.ctx, "xrp:usd")
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 100000000)), cs(c("usdx", 10000000))) suite.NoError(err)
err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 100000000), c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
id := suite.keeper.GetNextCdpID(suite.ctx) id := suite.keeper.GetNextCdpID(suite.ctx)
suite.Equal(uint64(2), id) suite.Equal(uint64(2), id)
@ -76,10 +78,10 @@ func (suite *CdpTestSuite) TestAddCdp() {
acc = ak.GetAccount(suite.ctx, addrs[0]) acc = ak.GetAccount(suite.ctx, addrs[0])
suite.Equal(cs(c("usdx", 10000000), c("xrp", 100000000), c("btc", 500000000)), acc.GetCoins()) suite.Equal(cs(c("usdx", 10000000), c("xrp", 100000000), c("btc", 500000000)), acc.GetCoins())
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("btc", 500000000)), cs(c("usdx", 26667000000))) err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("btc", 500000000), c("usdx", 26667000000))
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio))
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("btc", 500000000)), cs(c("usdx", 100000000))) err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("btc", 500000000), c("usdx", 100000000))
suite.NoError(err) suite.NoError(err)
id = suite.keeper.GetNextCdpID(suite.ctx) id = suite.keeper.GetNextCdpID(suite.ctx)
suite.Equal(uint64(3), id) suite.Equal(uint64(3), id)
@ -90,9 +92,9 @@ func (suite *CdpTestSuite) TestAddCdp() {
acc = ak.GetAccount(suite.ctx, addrs[0]) acc = ak.GetAccount(suite.ctx, addrs[0])
suite.Equal(cs(c("usdx", 110000000), c("xrp", 100000000)), acc.GetCoins()) suite.Equal(cs(c("usdx", 110000000), c("xrp", 100000000)), acc.GetCoins())
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("lol", 100)), cs(c("usdx", 10))) err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("lol", 100), c("usdx", 10))
suite.Require().True(errors.Is(err, types.ErrCollateralNotSupported)) suite.Require().True(errors.Is(err, types.ErrCollateralNotSupported))
err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 100)), cs(c("usdx", 10))) err = suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 100), c("usdx", 10))
suite.Require().True(errors.Is(err, types.ErrCdpAlreadyExists)) suite.Require().True(errors.Is(err, types.ErrCdpAlreadyExists))
} }
@ -120,8 +122,9 @@ func (suite *CdpTestSuite) TestGetNextCdpID() {
func (suite *CdpTestSuite) TestGetSetCdp() { func (suite *CdpTestSuite) TestGetSetCdp() {
_, addrs := app.GeneratePrivKeyAddressPairs(1) _, addrs := app.GeneratePrivKeyAddressPairs(1)
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 1)), cs(c("usdx", 1)), tmtime.Canonical(time.Now())) cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), c("usdx", 1), tmtime.Canonical(time.Now()))
suite.keeper.SetCDP(suite.ctx, cdp) err := suite.keeper.SetCDP(suite.ctx, cdp)
suite.NoError(err)
t, found := suite.keeper.GetCDP(suite.ctx, "xrp", types.DefaultCdpStartingID) t, found := suite.keeper.GetCDP(suite.ctx, "xrp", types.DefaultCdpStartingID)
suite.True(found) suite.True(found)
@ -135,8 +138,9 @@ func (suite *CdpTestSuite) TestGetSetCdp() {
func (suite *CdpTestSuite) TestGetSetCdpId() { func (suite *CdpTestSuite) TestGetSetCdpId() {
_, addrs := app.GeneratePrivKeyAddressPairs(2) _, addrs := app.GeneratePrivKeyAddressPairs(2)
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 1)), cs(c("usdx", 1)), tmtime.Canonical(time.Now())) cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), c("usdx", 1), tmtime.Canonical(time.Now()))
suite.keeper.SetCDP(suite.ctx, cdp) err := suite.keeper.SetCDP(suite.ctx, cdp)
suite.NoError(err)
suite.keeper.IndexCdpByOwner(suite.ctx, cdp) suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
id, found := suite.keeper.GetCdpID(suite.ctx, addrs[0], "xrp") id, found := suite.keeper.GetCdpID(suite.ctx, addrs[0], "xrp")
suite.True(found) suite.True(found)
@ -149,8 +153,9 @@ func (suite *CdpTestSuite) TestGetSetCdpId() {
func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndDenom() { func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndDenom() {
_, addrs := app.GeneratePrivKeyAddressPairs(2) _, addrs := app.GeneratePrivKeyAddressPairs(2)
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 1)), cs(c("usdx", 1)), tmtime.Canonical(time.Now())) cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), c("usdx", 1), tmtime.Canonical(time.Now()))
suite.keeper.SetCDP(suite.ctx, cdp) err := suite.keeper.SetCDP(suite.ctx, cdp)
suite.NoError(err)
suite.keeper.IndexCdpByOwner(suite.ctx, cdp) suite.keeper.IndexCdpByOwner(suite.ctx, cdp)
t, found := suite.keeper.GetCdpByOwnerAndDenom(suite.ctx, addrs[0], "xrp") t, found := suite.keeper.GetCdpByOwnerAndDenom(suite.ctx, addrs[0], "xrp")
suite.True(found) suite.True(found)
@ -164,31 +169,29 @@ func (suite *CdpTestSuite) TestGetSetCdpByOwnerAndDenom() {
func (suite *CdpTestSuite) TestCalculateCollateralToDebtRatio() { func (suite *CdpTestSuite) TestCalculateCollateralToDebtRatio() {
_, addrs := app.GeneratePrivKeyAddressPairs(1) _, addrs := app.GeneratePrivKeyAddressPairs(1)
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 3)), cs(c("usdx", 1)), tmtime.Canonical(time.Now())) cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 3), c("usdx", 1), tmtime.Canonical(time.Now()))
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal)
suite.Equal(sdk.MustNewDecFromStr("3.0"), cr) suite.Equal(sdk.MustNewDecFromStr("3.0"), cr)
cdp = types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 1)), cs(c("usdx", 2)), tmtime.Canonical(time.Now())) cdp = types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 1), c("usdx", 2), tmtime.Canonical(time.Now()))
cr = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal) cr = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal)
suite.Equal(sdk.MustNewDecFromStr("0.5"), cr) suite.Equal(sdk.MustNewDecFromStr("0.5"), cr)
cdp = types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 3)), cs(c("usdx", 1), c("susd", 2)), tmtime.Canonical(time.Now()))
cr = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal)
suite.Equal(sdk.MustNewDecFromStr("1"), cr)
} }
func (suite *CdpTestSuite) TestSetCdpByCollateralRatio() { func (suite *CdpTestSuite) TestSetCdpByCollateralRatio() {
_, addrs := app.GeneratePrivKeyAddressPairs(1) _, addrs := app.GeneratePrivKeyAddressPairs(1)
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 3)), cs(c("usdx", 1)), tmtime.Canonical(time.Now())) cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], c("xrp", 3), c("usdx", 1), tmtime.Canonical(time.Now()))
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal)
suite.NotPanics(func() { suite.keeper.IndexCdpByCollateralRatio(suite.ctx, cdp.Collateral[0].Denom, cdp.ID, cr) }) suite.NotPanics(func() { suite.keeper.IndexCdpByCollateralRatio(suite.ctx, cdp.Collateral.Denom, cdp.ID, cr) })
} }
func (suite *CdpTestSuite) TestIterateCdps() { func (suite *CdpTestSuite) TestIterateCdps() {
cdps := cdps() cdps := cdps()
for _, c := range cdps { for _, c := range cdps {
suite.keeper.SetCDP(suite.ctx, c) err := suite.keeper.SetCDP(suite.ctx, c)
suite.NoError(err)
suite.keeper.IndexCdpByOwner(suite.ctx, c) suite.keeper.IndexCdpByOwner(suite.ctx, c)
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal)
suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral[0].Denom, c.ID, cr) suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral.Denom, c.ID, cr)
} }
t := suite.keeper.GetAllCdps(suite.ctx) t := suite.keeper.GetAllCdps(suite.ctx)
suite.Equal(4, len(t)) suite.Equal(4, len(t))
@ -197,10 +200,11 @@ func (suite *CdpTestSuite) TestIterateCdps() {
func (suite *CdpTestSuite) TestIterateCdpsByDenom() { func (suite *CdpTestSuite) TestIterateCdpsByDenom() {
cdps := cdps() cdps := cdps()
for _, c := range cdps { for _, c := range cdps {
suite.keeper.SetCDP(suite.ctx, c) err := suite.keeper.SetCDP(suite.ctx, c)
suite.NoError(err)
suite.keeper.IndexCdpByOwner(suite.ctx, c) suite.keeper.IndexCdpByOwner(suite.ctx, c)
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal)
suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral[0].Denom, c.ID, cr) suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral.Denom, c.ID, cr)
} }
xrpCdps := suite.keeper.GetAllCdpsByDenom(suite.ctx, "xrp") xrpCdps := suite.keeper.GetAllCdpsByDenom(suite.ctx, "xrp")
suite.Equal(3, len(xrpCdps)) suite.Equal(3, len(xrpCdps))
@ -221,10 +225,11 @@ func (suite *CdpTestSuite) TestIterateCdpsByDenom() {
func (suite *CdpTestSuite) TestIterateCdpsByCollateralRatio() { func (suite *CdpTestSuite) TestIterateCdpsByCollateralRatio() {
cdps := cdps() cdps := cdps()
for _, c := range cdps { for _, c := range cdps {
suite.keeper.SetCDP(suite.ctx, c) err := suite.keeper.SetCDP(suite.ctx, c)
suite.NoError(err)
suite.keeper.IndexCdpByOwner(suite.ctx, c) suite.keeper.IndexCdpByOwner(suite.ctx, c)
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal)
suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral[0].Denom, c.ID, cr) suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral.Denom, c.ID, cr)
} }
xrpCdps := suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("1.25")) xrpCdps := suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("1.25"))
suite.Equal(0, len(xrpCdps)) suite.Equal(0, len(xrpCdps))
@ -237,51 +242,46 @@ func (suite *CdpTestSuite) TestIterateCdpsByCollateralRatio() {
suite.keeper.DeleteCDP(suite.ctx, cdps[0]) suite.keeper.DeleteCDP(suite.ctx, cdps[0])
suite.keeper.RemoveCdpOwnerIndex(suite.ctx, cdps[0]) suite.keeper.RemoveCdpOwnerIndex(suite.ctx, cdps[0])
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdps[0].Collateral, cdps[0].Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdps[0].Collateral, cdps[0].Principal)
suite.keeper.RemoveCdpCollateralRatioIndex(suite.ctx, cdps[0].Collateral[0].Denom, cdps[0].ID, cr) suite.keeper.RemoveCdpCollateralRatioIndex(suite.ctx, cdps[0].Collateral.Denom, cdps[0].ID, cr)
xrpCdps = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("2.0").Add(sdk.SmallestDec())) xrpCdps = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("2.0").Add(sdk.SmallestDec()))
suite.Equal(1, len(xrpCdps)) suite.Equal(1, len(xrpCdps))
} }
func (suite *CdpTestSuite) TestValidateCollateral() { func (suite *CdpTestSuite) TestValidateCollateral() {
c := sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(1))) c := sdk.NewCoin("xrp", sdk.NewInt(1))
err := suite.keeper.ValidateCollateral(suite.ctx, c) err := suite.keeper.ValidateCollateral(suite.ctx, c)
suite.NoError(err) suite.NoError(err)
c = sdk.NewCoins(sdk.NewCoin("lol", sdk.NewInt(1))) c = sdk.NewCoin("lol", sdk.NewInt(1))
err = suite.keeper.ValidateCollateral(suite.ctx, c) err = suite.keeper.ValidateCollateral(suite.ctx, c)
suite.Require().True(errors.Is(err, types.ErrCollateralNotSupported)) suite.Require().True(errors.Is(err, types.ErrCollateralNotSupported))
c = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1)), sdk.NewCoin("xrp", sdk.NewInt(1)))
err = suite.keeper.ValidateCollateral(suite.ctx, c)
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralLength))
} }
func (suite *CdpTestSuite) TestValidatePrincipal() { func (suite *CdpTestSuite) TestValidatePrincipal() {
d := sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10000000))) d := sdk.NewCoin("usdx", sdk.NewInt(10000000))
err := suite.keeper.ValidatePrincipalAdd(suite.ctx, d) err := suite.keeper.ValidatePrincipalAdd(suite.ctx, d)
suite.NoError(err) suite.NoError(err)
d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10000000)), sdk.NewCoin("susd", sdk.NewInt(10000000))) d = sdk.NewCoin("xusd", sdk.NewInt(1))
err = suite.keeper.ValidatePrincipalAdd(suite.ctx, d)
suite.NoError(err)
d = sdk.NewCoins(sdk.NewCoin("xusd", sdk.NewInt(1)))
err = suite.keeper.ValidatePrincipalAdd(suite.ctx, d) err = suite.keeper.ValidatePrincipalAdd(suite.ctx, d)
suite.Require().True(errors.Is(err, types.ErrDebtNotSupported)) suite.Require().True(errors.Is(err, types.ErrDebtNotSupported))
d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(1000000000001))) d = sdk.NewCoin("usdx", sdk.NewInt(1000000000001))
err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d) err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d)
suite.Require().True(errors.Is(err, types.ErrExceedsDebtLimit)) suite.Require().True(errors.Is(err, types.ErrExceedsDebtLimit))
d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(100000000))) d = sdk.NewCoin("usdx", sdk.NewInt(100000000))
err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d) err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d)
suite.NoError(err) suite.NoError(err)
} }
func (suite *CdpTestSuite) TestCalculateCollateralizationRatio() { func (suite *CdpTestSuite) TestCalculateCollateralizationRatio() {
c := cdps()[1] c := cdps()[1]
suite.keeper.SetCDP(suite.ctx, c) err := suite.keeper.SetCDP(suite.ctx, c)
suite.NoError(err)
suite.keeper.IndexCdpByOwner(suite.ctx, c) suite.keeper.IndexCdpByOwner(suite.ctx, c)
cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal) cr := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, c.Collateral, c.Principal)
suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral[0].Denom, c.ID, cr) suite.keeper.IndexCdpByCollateralRatio(suite.ctx, c.Collateral.Denom, c.ID, cr)
cr, err := suite.keeper.CalculateCollateralizationRatio(suite.ctx, c.Collateral, c.Principal, c.AccumulatedFees) cr, err = suite.keeper.CalculateCollateralizationRatio(suite.ctx, c.Collateral, c.Principal, c.AccumulatedFees)
suite.NoError(err) suite.NoError(err)
suite.Equal(d("2.5"), cr) suite.Equal(d("2.5"), cr)
c.AccumulatedFees = sdk.NewCoins(sdk.NewCoin("usdx", i(10000000))) c.AccumulatedFees = sdk.NewCoin("usdx", i(10000000))
cr, err = suite.keeper.CalculateCollateralizationRatio(suite.ctx, c.Collateral, c.Principal, c.AccumulatedFees) cr, err = suite.keeper.CalculateCollateralizationRatio(suite.ctx, c.Collateral, c.Principal, c.AccumulatedFees)
suite.NoError(err) suite.NoError(err)
suite.Equal(d("1.25"), cr) suite.Equal(d("1.25"), cr)

View File

@ -10,23 +10,23 @@ import (
) )
// DepositCollateral adds collateral to a cdp // DepositCollateral adds collateral to a cdp
func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) error { func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coin) error {
err := k.ValidateCollateral(ctx, collateral) err := k.ValidateCollateral(ctx, collateral)
if err != nil { if err != nil {
return err return err
} }
cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral[0].Denom) cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral.Denom)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateral[0].Denom) return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateral.Denom)
} }
deposit, found := k.GetDeposit(ctx, 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 {
deposit = types.NewDeposit(cdp.ID, depositor, collateral) deposit = types.NewDeposit(cdp.ID, depositor, collateral)
} }
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, depositor, types.ModuleName, collateral) err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, depositor, types.ModuleName, sdk.NewCoins(collateral))
if err != nil { if err != nil {
return err return err
} }
@ -40,31 +40,33 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, deposit
k.SetDeposit(ctx, deposit) k.SetDeposit(ctx, deposit)
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral.Denom, cdp.ID, oldCollateralToDebtRatio)
cdp.FeesUpdated = ctx.BlockTime() cdp.Collateral = cdp.Collateral.Add(collateral)
cdp.Collateral = cdp.Collateral.Add(collateral...) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) if err != nil {
return err
}
return nil return nil
} }
// WithdrawCollateral removes collateral from a cdp if it does not put the cdp below the liquidation ratio // WithdrawCollateral removes collateral from a cdp if it does not put the cdp below the liquidation ratio
func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) error { func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coin) error {
err := k.ValidateCollateral(ctx, collateral) err := k.ValidateCollateral(ctx, collateral)
if err != nil { if err != nil {
return err return err
} }
cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral[0].Denom) cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral.Denom)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateral[0].Denom) return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateral.Denom)
} }
deposit, found := k.GetDeposit(ctx, cdp.ID, depositor) deposit, found := k.GetDeposit(ctx, cdp.ID, depositor)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrDepositNotFound, "depositor %s, collateral %s", depositor, collateral[0].Denom) return sdkerrors.Wrapf(types.ErrDepositNotFound, "depositor %s, collateral %s", depositor, collateral.Denom)
} }
if collateral.IsAnyGT(deposit.Amount) { if collateral.Amount.GT(deposit.Amount.Amount) {
return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.Amount) return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.Amount)
} }
@ -72,9 +74,9 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi
if err != nil { if err != nil {
return err return err
} }
liquidationRatio := k.getLiquidationRatio(ctx, collateral[0].Denom) liquidationRatio := k.getLiquidationRatio(ctx, collateral.Denom)
if collateralizationRatio.LT(liquidationRatio) { if collateralizationRatio.LT(liquidationRatio) {
return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ration %s", collateral[0].Denom, collateralizationRatio, liquidationRatio) return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ration %s", collateral.Denom, collateralizationRatio, liquidationRatio)
} }
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
@ -84,17 +86,19 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi
), ),
) )
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, collateral) err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, depositor, sdk.NewCoins(collateral))
if err != nil { if err != nil {
panic(err) panic(err)
} }
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral.Denom, cdp.ID, oldCollateralToDebtRatio)
cdp.FeesUpdated = ctx.BlockTime()
cdp.Collateral = cdp.Collateral.Sub(collateral) cdp.Collateral = cdp.Collateral.Sub(collateral)
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
if err != nil {
return err
}
deposit.Amount = deposit.Amount.Sub(collateral) deposit.Amount = deposit.Amount.Sub(collateral)
if deposit.Amount.IsZero() { if deposit.Amount.IsZero() {

View File

@ -41,14 +41,14 @@ func (suite *DepositTestSuite) SetupTest() {
suite.keeper = keeper suite.keeper = keeper
suite.ctx = ctx suite.ctx = ctx
suite.addrs = addrs suite.addrs = addrs
err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 400000000)), cs(c("usdx", 10000000))) err := suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 400000000), c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
} }
func (suite *DepositTestSuite) TestGetSetDeposit() { func (suite *DepositTestSuite) TestGetSetDeposit() {
d, found := suite.keeper.GetDeposit(suite.ctx, 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], 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))
@ -61,32 +61,32 @@ 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], c("xrp", 10000000))
suite.NoError(err) suite.NoError(err)
d, found := suite.keeper.GetDeposit(suite.ctx, 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], c("xrp", 410000000))
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))
cd, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) cd, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
suite.Equal(cs(c("xrp", 410000000)), cd.Collateral) suite.Equal(c("xrp", 410000000), cd.Collateral)
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(90000000), acc.GetCoins().AmountOf("xrp")) suite.Equal(i(90000000), acc.GetCoins().AmountOf("xrp"))
err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("btc", 1))) err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], c("btc", 1))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], cs(c("xrp", 1))) err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], c("xrp", 1))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
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], c("xrp", 10000000))
suite.NoError(err) suite.NoError(err)
d, found = suite.keeper.GetDeposit(suite.ctx, 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], c("xrp", 10000000))
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(2, len(ds)) suite.Equal(2, len(ds))
@ -94,29 +94,30 @@ func (suite *DepositTestSuite) TestDepositCollateral() {
} }
func (suite *DepositTestSuite) TestWithdrawCollateral() { func (suite *DepositTestSuite) TestWithdrawCollateral() {
err := suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 400000000))) err := suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], c("xrp", 400000000))
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio))
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 321000000))) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], c("xrp", 321000000))
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio))
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], c("xrp", 10000000))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
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 = c("usdx", 1)
suite.keeper.SetCDP(suite.ctx, cd) err = suite.keeper.SetCDP(suite.ctx, cd)
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 320000000))) suite.NoError(err)
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], c("xrp", 320000000))
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio))
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], c("xrp", 10000000))
suite.NoError(err) suite.NoError(err)
dep, _ := suite.keeper.GetDeposit(suite.ctx, 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], c("xrp", 390000000))
suite.True(dep.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"))
err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[1], cs(c("xrp", 10000000))) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[1], c("xrp", 10000000))
suite.Require().True(errors.Is(err, types.ErrDepositNotFound)) suite.Require().True(errors.Is(err, types.ErrDepositNotFound))
} }

View File

@ -9,33 +9,33 @@ import (
) )
// AddPrincipal adds debt to a cdp if the additional debt does not put the cdp below the liquidation ratio // AddPrincipal adds debt to a cdp if the additional debt does not put the cdp below the liquidation ratio
func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, principal sdk.Coins) error { func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, principal sdk.Coin) error {
// validation // validation
cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, denom) cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, denom)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom) return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom)
} }
err := k.ValidatePrincipalDraw(ctx, principal) err := k.ValidatePrincipalDraw(ctx, principal, cdp.Principal.Denom)
if err != nil { if err != nil {
return err return err
} }
err = k.ValidateDebtLimit(ctx, cdp.Collateral[0].Denom, principal) err = k.ValidateDebtLimit(ctx, cdp.Collateral.Denom, principal)
if err != nil { if err != nil {
return err return err
} }
err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal...), cdp.AccumulatedFees) err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal), cdp.AccumulatedFees)
if err != nil { if err != nil {
return err return err
} }
// mint the principal and send it to the cdp owner // mint the principal and send it to the cdp owner
err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, principal) err = k.supplyKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(principal))
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, owner, principal) err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, owner, sdk.NewCoins(principal))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -56,61 +56,59 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string
) )
// remove old collateral:debt index // remove old collateral:debt index
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.RemoveCdpCollateralRatioIndex(ctx, denom, cdp.ID, oldCollateralToDebtRatio) k.RemoveCdpCollateralRatioIndex(ctx, denom, cdp.ID, oldCollateralToDebtRatio)
// update cdp state // update cdp state
cdp.Principal = cdp.Principal.Add(principal...) cdp.Principal = cdp.Principal.Add(principal)
cdp.FeesUpdated = ctx.BlockTime()
// increment total principal for the input collateral type // increment total principal for the input collateral type
k.IncrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, principal) k.IncrementTotalPrincipal(ctx, cdp.Collateral.Denom, principal)
// set cdp state and indexes in the store // set cdp state and indexes in the store
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
if err != nil {
return err
}
return nil return nil
} }
// RepayPrincipal removes debt from the cdp // RepayPrincipal removes debt from the cdp
// If all debt is repaid, the collateral is returned to depositors and the cdp is removed from the store // If all debt is repaid, the collateral is returned to depositors and the cdp is removed from the store
func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, payment sdk.Coins) error { func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, payment sdk.Coin) error {
// validation // validation
cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, denom) cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, denom)
if !found { if !found {
return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom) return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom)
} }
err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees...)) err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees))
if err != nil { if err != nil {
return err return err
} }
// calculate fee and principal payment // calculate fee and principal payment
feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), cdp.AccumulatedFees, payment) feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees), cdp.AccumulatedFees, payment)
// send the payment from the sender to the cpd module // send the payment from the sender to the cpd module
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, feePayment.Add(principalPayment...)) err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, sdk.NewCoins(feePayment.Add(principalPayment)))
if err != nil { if err != nil {
return err return err
} }
// burn the payment coins // burn the payment coins
err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, feePayment.Add(principalPayment...)) err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(feePayment.Add(principalPayment)))
if err != nil { if err != nil {
panic(err) panic(err)
} }
// burn the corresponding amount of debt coins // burn the corresponding amount of debt coins
cdpDebt := k.getModAccountDebt(ctx, types.ModuleName) cdpDebt := k.getModAccountDebt(ctx, types.ModuleName)
paymentAmount := sdk.ZeroInt() paymentAmount := feePayment.Amount.Add(principalPayment.Amount)
for _, c := range feePayment.Add(principalPayment...) { coinsToBurn := sdk.NewCoin(k.GetDebtDenom(ctx), paymentAmount)
paymentAmount = paymentAmount.Add(c.Amount)
}
coinsToBurn := sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), paymentAmount))
if paymentAmount.GT(cdpDebt) { if paymentAmount.GT(cdpDebt) {
coinsToBurn = sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), cdpDebt)) coinsToBurn = sdk.NewCoin(k.GetDebtDenom(ctx), cdpDebt)
} }
err = k.BurnDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), coinsToBurn) err = k.BurnDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), coinsToBurn)
if err != nil { if err != nil {
@ -121,13 +119,13 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri
ctx.EventManager().EmitEvent( ctx.EventManager().EmitEvent(
sdk.NewEvent( sdk.NewEvent(
types.EventTypeCdpRepay, types.EventTypeCdpRepay,
sdk.NewAttribute(sdk.AttributeKeyAmount, feePayment.Add(principalPayment...).String()), sdk.NewAttribute(sdk.AttributeKeyAmount, feePayment.Add(principalPayment).String()),
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)), sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
), ),
) )
// remove the old collateral:debt ratio index // remove the old collateral:debt ratio index
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.RemoveCdpCollateralRatioIndex(ctx, denom, cdp.ID, oldCollateralToDebtRatio) k.RemoveCdpCollateralRatioIndex(ctx, denom, cdp.ID, oldCollateralToDebtRatio)
// update cdp state // update cdp state
@ -135,10 +133,9 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri
cdp.Principal = cdp.Principal.Sub(principalPayment) cdp.Principal = cdp.Principal.Sub(principalPayment)
} }
cdp.AccumulatedFees = cdp.AccumulatedFees.Sub(feePayment) cdp.AccumulatedFees = cdp.AccumulatedFees.Sub(feePayment)
cdp.FeesUpdated = ctx.BlockTime()
// decrement the total principal for the input collateral type // decrement the total principal for the input collateral type
k.DecrementTotalPrincipal(ctx, denom, feePayment.Add(principalPayment...)) k.DecrementTotalPrincipal(ctx, denom, feePayment.Add(principalPayment))
// if the debt is fully paid, return collateral to depositors, // if the debt is fully paid, return collateral to depositors,
// and remove the cdp and indexes from the store // and remove the cdp and indexes from the store
@ -158,31 +155,26 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri
} }
// set cdp state and update indexes // set cdp state and update indexes
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) err = k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
if err != nil {
return err
}
return nil return nil
} }
// ValidatePaymentCoins validates that the input coins are valid for repaying debt // ValidatePaymentCoins validates that the input coins are valid for repaying debt
func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk.Coins, debt sdk.Coins) error { func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk.Coin, debt sdk.Coin) error {
subset := payment.DenomsSubsetOf(cdp.Principal) if payment.Denom != debt.Denom {
if !subset { return sdkerrors.Wrapf(types.ErrInvalidPayment, "cdp %d: expected %s, got %s", cdp.ID, debt.Denom, payment.Denom)
var paymentDenoms []string
var principalDenoms []string
for _, pc := range cdp.Principal {
principalDenoms = append(principalDenoms, pc.Denom)
}
for _, pc := range payment {
paymentDenoms = append(paymentDenoms, pc.Denom)
}
return sdkerrors.Wrapf(types.ErrInvalidPayment, "cdp %d: expected %s, got %s", cdp.ID, principalDenoms, paymentDenoms)
} }
for _, dc := range payment { dp, found := k.GetDebtParam(ctx, payment.Denom)
dp, _ := k.GetDebtParam(ctx, dc.Denom) if !found {
proposedBalance := cdp.Principal.AmountOf(dc.Denom).Sub(dc.Amount) return sdkerrors.Wrapf(types.ErrInvalidPayment, "payment denom %s not found", payment.Denom)
if proposedBalance.GT(sdk.ZeroInt()) && proposedBalance.LT(dp.DebtFloor) { }
return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", sdk.NewCoins(sdk.NewCoin(dc.Denom, proposedBalance)), dp.DebtFloor) proposedBalance := cdp.Principal.Amount.Sub(payment.Amount)
} if proposedBalance.GT(sdk.ZeroInt()) && proposedBalance.LT(dp.DebtFloor) {
return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", sdk.NewCoin(payment.Denom, proposedBalance), dp.DebtFloor)
} }
return nil return nil
} }
@ -191,7 +183,7 @@ func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk
func (k Keeper) ReturnCollateral(ctx sdk.Context, cdp types.CDP) { func (k Keeper) ReturnCollateral(ctx sdk.Context, cdp types.CDP) {
deposits := k.GetDeposits(ctx, cdp.ID) deposits := k.GetDeposits(ctx, cdp.ID)
for _, deposit := range deposits { for _, deposit := range deposits {
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, deposit.Depositor, deposit.Amount) err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, deposit.Depositor, sdk.NewCoins(deposit.Amount))
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -199,31 +191,30 @@ func (k Keeper) ReturnCollateral(ctx sdk.Context, cdp types.CDP) {
} }
} }
func (k Keeper) calculatePayment(ctx sdk.Context, owed sdk.Coins, fees sdk.Coins, payment sdk.Coins) (sdk.Coins, sdk.Coins) { func (k Keeper) calculatePayment(ctx sdk.Context, owed sdk.Coin, fees sdk.Coin, payment sdk.Coin) (sdk.Coin, sdk.Coin) {
// divides repayment into principal and fee components, with fee payment applied first. // divides repayment into principal and fee components, with fee payment applied first.
feePayment := sdk.NewCoins() feePayment := sdk.NewCoin(payment.Denom, sdk.ZeroInt())
principalPayment := sdk.NewCoins() principalPayment := sdk.NewCoin(payment.Denom, sdk.ZeroInt())
overpayment := sdk.NewCoins() overpayment := sdk.NewCoin(payment.Denom, sdk.ZeroInt())
// TODO must compare denoms directly if there are multiple principal denoms if !payment.Amount.IsPositive() {
if payment.IsAllGT(owed) { return feePayment, principalPayment
}
// check for over payment
if payment.Amount.GT(owed.Amount) {
overpayment = payment.Sub(owed) overpayment = payment.Sub(owed)
payment = payment.Sub(overpayment) payment = payment.Sub(overpayment)
} }
// if no fees, 100% of payment is principal payment
if fees.IsZero() { if fees.IsZero() {
return sdk.NewCoins(), payment return feePayment, payment
} }
for _, fc := range fees { // pay fees before repaying principal
if payment.AmountOf(fc.Denom).IsPositive() { if payment.Amount.GT(fees.Amount) {
if payment.AmountOf(fc.Denom).GT(fc.Amount) { feePayment = fees
feePayment = feePayment.Add(fc) principalPayment = payment.Sub(fees)
pc := sdk.NewCoin(fc.Denom, payment.AmountOf(fc.Denom).Sub(fc.Amount)) } else {
principalPayment = principalPayment.Add(pc) feePayment = payment
} else {
fc := sdk.NewCoin(fc.Denom, payment.AmountOf(fc.Denom))
feePayment = feePayment.Add(fc)
}
}
} }
return feePayment, principalPayment return feePayment, principalPayment
} }

View File

@ -43,18 +43,19 @@ func (suite *DrawTestSuite) SetupTest() {
suite.keeper = keeper suite.keeper = keeper
suite.ctx = ctx suite.ctx = ctx
suite.addrs = addrs suite.addrs = addrs
err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 400000000)), cs(c("usdx", 10000000))) err := suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 400000000), c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
} }
func (suite *DrawTestSuite) TestAddRepayPrincipal() { func (suite *DrawTestSuite) TestAddRepayPrincipal() {
err := suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) err := suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
t, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) t, found := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
suite.Equal(cs(c("usdx", 20000000)), t.Principal) suite.True(found)
ctd := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...)) suite.Equal(c("usdx", 20000000), t.Principal)
ctd := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees))
suite.Equal(d("20.0"), ctd) suite.Equal(d("20.0"), ctd)
ts := suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("20.0")) ts := suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("20.0"))
suite.Equal(0, len(ts)) suite.Equal(0, len(ts))
@ -66,74 +67,44 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() {
acc := sk.GetModuleAccount(suite.ctx, types.ModuleName) acc := sk.GetModuleAccount(suite.ctx, types.ModuleName)
suite.Equal(cs(c("xrp", 400000000), c("debt", 20000000)), acc.GetCoins()) suite.Equal(cs(c("xrp", 400000000), c("debt", 20000000)), acc.GetCoins())
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("susd", 10000000))) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", c("susd", 10000000))
suite.NoError(err) suite.Require().True(errors.Is(err, types.ErrInvalidDebtRequest))
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
suite.Equal(cs(c("usdx", 20000000), c("susd", 10000000)), t.Principal)
ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...))
suite.Equal(d("400000000").Quo(d("30000000")), ctd)
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("400").Quo(d("30")))
suite.Equal(0, len(ts))
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("400").Quo(d("30")).Add(sdk.SmallestDec()))
suite.Equal(ts[0], t)
tp = suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "susd")
suite.Equal(i(10000000), tp)
sk = suite.app.GetSupplyKeeper()
acc = sk.GetModuleAccount(suite.ctx, types.ModuleName)
suite.Equal(cs(c("xrp", 400000000), c("debt", 30000000)), acc.GetCoins())
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[1], "xrp", cs(c("usdx", 10000000))) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[1], "xrp", c("usdx", 10000000))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("xusd", 10000000))) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", c("xusd", 10000000))
suite.Require().True(errors.Is(err, types.ErrDebtNotSupported)) suite.Require().True(errors.Is(err, types.ErrInvalidDebtRequest))
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 311000000))) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", c("usdx", 311000000))
suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio))
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) t, found = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
suite.Equal(cs(c("usdx", 10000000), c("susd", 10000000)), t.Principal) suite.True(found)
ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...)) suite.Equal(c("usdx", 10000000), t.Principal)
suite.Equal(d("20.0"), ctd)
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("20.0"))
suite.Equal(0, len(ts))
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("20.0").Add(sdk.SmallestDec()))
suite.Equal(ts[0], t)
tp = suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
suite.Equal(i(10000000), tp)
sk = suite.app.GetSupplyKeeper()
acc = sk.GetModuleAccount(suite.ctx, types.ModuleName)
suite.Equal(cs(c("xrp", 400000000), c("debt", 20000000)), acc.GetCoins())
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("susd", 10000000))) ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees))
suite.NoError(err)
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
suite.Equal(cs(c("usdx", 10000000)), t.Principal)
ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...))
suite.Equal(d("40.0"), ctd) suite.Equal(d("40.0"), ctd)
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("40.0")) ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("40.0"))
suite.Equal(0, len(ts)) suite.Equal(0, len(ts))
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("40.0").Add(sdk.SmallestDec())) ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("40.0").Add(sdk.SmallestDec()))
suite.Equal(ts[0], t) suite.Equal(ts[0], t)
tp = suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "susd")
suite.Equal(i(0), tp)
sk = suite.app.GetSupplyKeeper() sk = suite.app.GetSupplyKeeper()
acc = sk.GetModuleAccount(suite.ctx, types.ModuleName) acc = sk.GetModuleAccount(suite.ctx, types.ModuleName)
suite.Equal(cs(c("xrp", 400000000), c("debt", 10000000)), acc.GetCoins()) suite.Equal(cs(c("xrp", 400000000), c("debt", 10000000)), acc.GetCoins())
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("xusd", 10000000))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", c("xusd", 10000000))
suite.Require().True(errors.Is(err, types.ErrInvalidPayment)) suite.Require().True(errors.Is(err, types.ErrInvalidPayment))
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[1], "xrp", cs(c("xusd", 10000000))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[1], "xrp", c("xusd", 10000000))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 9000000))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", c("usdx", 9000000))
suite.Require().True(errors.Is(err, types.ErrBelowDebtFloor)) suite.Require().True(errors.Is(err, types.ErrBelowDebtFloor))
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
_, found := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) _, found = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1))
suite.False(found) suite.False(found)
ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", types.MaxSortableDec) ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", types.MaxSortableDec)
suite.Equal(0, len(ts)) suite.Equal(0, len(ts))
@ -146,7 +117,7 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() {
} }
func (suite *DrawTestSuite) TestRepayPrincipalOverpay() { func (suite *DrawTestSuite) TestRepayPrincipalOverpay() {
err := suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 20000000))) err := suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", c("usdx", 20000000))
suite.NoError(err) suite.NoError(err)
ak := suite.app.GetAccountKeeper() ak := suite.app.GetAccountKeeper()
acc := ak.GetAccount(suite.ctx, suite.addrs[0]) acc := ak.GetAccount(suite.ctx, suite.addrs[0])
@ -156,43 +127,43 @@ func (suite *DrawTestSuite) TestRepayPrincipalOverpay() {
} }
func (suite *DrawTestSuite) TestAddRepayPrincipalFees() { func (suite *DrawTestSuite) TestAddRepayPrincipalFees() {
err := suite.keeper.AddCdp(suite.ctx, suite.addrs[2], cs(c("xrp", 1000000000000)), cs(c("usdx", 100000000000))) err := suite.keeper.AddCdp(suite.ctx, suite.addrs[2], c("xrp", 1000000000000), c("usdx", 100000000000))
suite.NoError(err) suite.NoError(err)
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Minute * 10)) suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Minute * 10))
err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp") err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp")
suite.NoError(err) suite.NoError(err)
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 10000000))) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
t, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) t, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
suite.Equal(cs(c("usdx", 92827)), t.AccumulatedFees) suite.Equal(c("usdx", 92827), t.AccumulatedFees)
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp", c("usdx", 100))
suite.NoError(err) suite.NoError(err)
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
suite.Equal(cs(c("usdx", 92727)), t.AccumulatedFees) suite.Equal(c("usdx", 92727), t.AccumulatedFees)
err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100010092727))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp", c("usdx", 100010092727))
suite.NoError(err) suite.NoError(err)
_, f := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) _, f := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
suite.False(f) suite.False(f)
err = suite.keeper.AddCdp(suite.ctx, suite.addrs[2], cs(c("xrp", 1000000000000)), cs(c("usdx", 100000000))) err = suite.keeper.AddCdp(suite.ctx, suite.addrs[2], c("xrp", 1000000000000), c("usdx", 100000000))
suite.NoError(err) suite.NoError(err)
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000)) // move forward one year in time suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000)) // move forward one year in time
err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp") err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp")
suite.NoError(err) suite.NoError(err)
err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100000000))) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", c("usdx", 100000000))
suite.NoError(err) suite.NoError(err)
t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(3)) t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(3))
suite.Equal(cs(c("usdx", 5000000)), t.AccumulatedFees) suite.Equal(c("usdx", 5000000), t.AccumulatedFees)
} }
func (suite *DrawTestSuite) TestPricefeedFailure() { func (suite *DrawTestSuite) TestPricefeedFailure() {
ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2)) ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2))
pfk := suite.app.GetPriceFeedKeeper() pfk := suite.app.GetPriceFeedKeeper()
pfk.SetCurrentPrices(ctx, "xrp:usd") pfk.SetCurrentPrices(ctx, "xrp:usd")
err := suite.keeper.AddPrincipal(ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) err := suite.keeper.AddPrincipal(ctx, suite.addrs[0], "xrp", c("usdx", 10000000))
suite.Error(err) suite.Error(err)
err = suite.keeper.RepayPrincipal(ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) err = suite.keeper.RepayPrincipal(ctx, suite.addrs[0], "xrp", c("usdx", 10000000))
suite.NoError(err) suite.NoError(err)
} }
@ -203,7 +174,7 @@ func (suite *DrawTestSuite) TestModuleAccountFailure() {
acc := sk.GetModuleAccount(ctx, types.ModuleName) acc := sk.GetModuleAccount(ctx, types.ModuleName)
ak := suite.app.GetAccountKeeper() ak := suite.app.GetAccountKeeper()
ak.RemoveAccount(ctx, acc) ak.RemoveAccount(ctx, acc)
_ = suite.keeper.RepayPrincipal(ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) suite.keeper.RepayPrincipal(ctx, suite.addrs[0], "xrp", c("usdx", 10000000))
}) })
} }

View File

@ -8,29 +8,25 @@ import (
// CalculateFees returns the fees accumulated since fees were last calculated based on // CalculateFees returns the fees accumulated since fees were last calculated based on
// the input amount of outstanding debt (principal) and the number of periods (seconds) that have passed // the input amount of outstanding debt (principal) and the number of periods (seconds) that have passed
func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coins, periods sdk.Int, denom string) sdk.Coins { func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coin, periods sdk.Int, denom string) sdk.Coin {
newFees := sdk.NewCoins() // how fees are calculated:
for _, pc := range principal { // feesAccumulated = (outstandingDebt * (feeRate^periods)) - outstandingDebt
// how fees are calculated: // Note that since we can't do x^y using sdk.Decimal, we are converting to int and using RelativePow
// feesAccumulated = (outstandingDebt * (feeRate^periods)) - outstandingDebt feePerSecond := k.getFeeRate(ctx, denom)
// Note that since we can't do x^y using sdk.Decimal, we are converting to int and using RelativePow scalar := sdk.NewInt(1000000000000000000)
feePerSecond := k.getFeeRate(ctx, denom) feeRateInt := feePerSecond.Mul(sdk.NewDecFromInt(scalar)).TruncateInt()
scalar := sdk.NewInt(1000000000000000000) accumulator := sdk.NewDecFromInt(types.RelativePow(feeRateInt, periods, scalar)).Mul(sdk.SmallestDec())
feeRateInt := feePerSecond.Mul(sdk.NewDecFromInt(scalar)).TruncateInt() feesAccumulated := (sdk.NewDecFromInt(principal.Amount).Mul(accumulator)).Sub(sdk.NewDecFromInt(principal.Amount))
accumulator := sdk.NewDecFromInt(types.RelativePow(feeRateInt, periods, scalar)).Mul(sdk.SmallestDec()) newFees := sdk.NewCoin(principal.Denom, feesAccumulated.TruncateInt())
feesAccumulated := (sdk.NewDecFromInt(pc.Amount).Mul(accumulator)).Sub(sdk.NewDecFromInt(pc.Amount))
// TODO this will always round down, causing precision loss between the sum of all fees in CDPs and surplus coins in liquidator account
newFees = newFees.Add(sdk.NewCoin(pc.Denom, feesAccumulated.TruncateInt()))
}
return newFees return newFees
} }
// UpdateFeesForAllCdps updates the fees for each of the CDPs // UpdateFeesForAllCdps updates the fees for each of the CDPs
func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) error { func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) error {
var iterationErr error
k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool { k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool {
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
newFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom) newFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom)
@ -42,67 +38,69 @@ func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) er
} }
// note - only works if principal length is one // note - only works if principal length is one
for _, dc := range cdp.Principal { dp, found := k.GetDebtParam(ctx, cdp.Principal.Denom)
dp, found := k.GetDebtParam(ctx, dc.Denom) if !found {
if !found { return false
return false
}
savingsRate := dp.SavingsRate
newFeesSavings := sdk.NewDecFromInt(newFees.AmountOf(dp.Denom)).Mul(savingsRate).RoundInt()
newFeesSurplus := newFees.AmountOf(dp.Denom).Sub(newFeesSavings)
// similar to checking for rounding to zero of all fees, but in this case we
// need to handle cases where we expect surplus or savings fees to be zero, namely
// if newFeesSavings = 0, check if savings rate is not zero
// if newFeesSurplus = 0, check if savings rate is not one
if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) {
return false
}
// mint debt coins to the cdp account
k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom)
feeCoins := sdk.NewCoins(sdk.NewCoin(dp.Denom, previousDebt))
k.SetTotalPrincipal(ctx, collateralDenom, dp.Denom, feeCoins.Add(newFees...).AmountOf(dp.Denom))
// mint surplus coins divided between the liquidator and savings module accounts.
k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus)))
k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSavings)))
} }
savingsRate := dp.SavingsRate
newFeesSavings := sdk.NewDecFromInt(newFees.Amount).Mul(savingsRate).RoundInt()
newFeesSurplus := newFees.Amount.Sub(newFeesSavings)
// similar to checking for rounding to zero of all fees, but in this case we
// need to handle cases where we expect surplus or savings fees to be zero, namely
// if newFeesSavings = 0, check if savings rate is not zero
// if newFeesSurplus = 0, check if savings rate is not one
if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) {
return false
}
// mint debt coins to the cdp account
k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom)
newDebt := previousDebt.Add(newFees.Amount)
k.SetTotalPrincipal(ctx, collateralDenom, dp.Denom, newDebt)
// mint surplus coins divided between the liquidator and savings module accounts.
k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus)))
k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSavings)))
// now add the new fees fees to the accumulated fees for the cdp // now add the new fees fees to the accumulated fees for the cdp
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(newFees...) cdp.AccumulatedFees = cdp.AccumulatedFees.Add(newFees)
// and set the fees updated time to the current block time since we just updated it // and set the fees updated time to the current block time since we just updated it
cdp.FeesUpdated = ctx.BlockTime() cdp.FeesUpdated = ctx.BlockTime()
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral.Denom, cdp.ID, oldCollateralToDebtRatio)
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) err := k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
if err != nil {
iterationErr = err
return true
}
return false // this returns true when you want to stop iterating. Since we want to iterate through all we return false return false // this returns true when you want to stop iterating. Since we want to iterate through all we return false
}) })
if iterationErr != nil {
return iterationErr
}
return nil return nil
} }
// IncrementTotalPrincipal increments the total amount of debt that has been drawn with that collateral type // IncrementTotalPrincipal increments the total amount of debt that has been drawn with that collateral type
func (k Keeper) IncrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coins) { func (k Keeper) IncrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coin) {
for _, pc := range principal { total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom)
total := k.GetTotalPrincipal(ctx, collateralDenom, pc.Denom) total = total.Add(principal.Amount)
total = total.Add(pc.Amount) k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total)
k.SetTotalPrincipal(ctx, collateralDenom, pc.Denom, total)
}
} }
// DecrementTotalPrincipal decrements the total amount of debt that has been drawn for a particular collateral type // DecrementTotalPrincipal decrements the total amount of debt that has been drawn for a particular collateral type
func (k Keeper) DecrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coins) { func (k Keeper) DecrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coin) {
for _, pc := range principal { total := k.GetTotalPrincipal(ctx, collateralDenom, principal.Denom)
total := k.GetTotalPrincipal(ctx, collateralDenom, pc.Denom) total = total.Sub(principal.Amount)
total = total.Sub(pc.Amount) if total.IsNegative() {
if total.IsNegative() { // can happen in tests due to rounding errors in fee calculation
// can happen in tests due to rounding errors in fee calculation total = sdk.ZeroInt()
total = sdk.ZeroInt()
}
k.SetTotalPrincipal(ctx, collateralDenom, pc.Denom, total)
} }
k.SetTotalPrincipal(ctx, collateralDenom, principal.Denom, total)
} }
// GetTotalPrincipal returns the total amount of principal that has been drawn for a particular collateral // GetTotalPrincipal returns the total amount of principal that has been drawn for a particular collateral

View File

@ -1,12 +1,10 @@
package keeper_test package keeper_test
import ( import (
"math/rand"
"testing" "testing"
"time" "time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"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/cdp/keeper" "github.com/kava-labs/kava/x/cdp/keeper"
@ -36,43 +34,6 @@ func (suite *FeeTestSuite) SetupTest() {
suite.keeper = keeper suite.keeper = keeper
} }
func (suite *FeeTestSuite) TestCalculateFeesPrecisionLoss() {
// Calculates the difference between fees calculated on the total amount of debt,
// versus iterating over all the 1000 randomly generated cdps.
// Assumes 7 second block times, runs simulations for 100, 1000, 10000, 100000, and 1000000
// blocks, where the bulk debt is updated each block, and the cdps are updated once.
coins := []sdk.Coins{}
total := sdk.NewCoins()
for i := 0; i < 1000; i++ {
ri, err := simulation.RandPositiveInt(rand.New(rand.NewSource(int64(i))), sdk.NewInt(100000000000))
suite.NoError(err)
c := sdk.NewCoins(sdk.NewCoin("usdx", ri))
coins = append(coins, c)
total = total.Add(sdk.NewCoin("usdx", ri))
}
numBlocks := []int{100, 1000, 10000, 100000}
for _, nb := range numBlocks {
bulkFees := sdk.NewCoins()
individualFees := sdk.NewCoins()
for x := 0; x < nb; x++ {
fee := suite.keeper.CalculateFees(suite.ctx, total.Add(bulkFees...), i(7), "xrp")
bulkFees = bulkFees.Add(fee...)
}
for _, cns := range coins {
fee := suite.keeper.CalculateFees(suite.ctx, cns, i(int64(nb*7)), "xrp")
individualFees = individualFees.Add(fee...)
}
absError := (sdk.OneDec().Sub(sdk.NewDecFromInt(bulkFees[0].Amount).Quo(sdk.NewDecFromInt(individualFees[0].Amount)))).Abs()
suite.True(d("0.00001").GTE(absError))
}
}
// createCdps is a helper function to create two CDPs each with zero fees // createCdps is a helper function to create two CDPs each with zero fees
func (suite *FeeTestSuite) createCdps() { func (suite *FeeTestSuite) createCdps() {
// create 2 accounts in the state and give them some coins // create 2 accounts in the state and give them some coins
@ -91,11 +52,14 @@ func (suite *FeeTestSuite) createCdps() {
// now create two cdps with the addresses we just created // now create two cdps with the addresses we just created
// use the created account to create a cdp that SHOULD have fees updated // use the created account to create a cdp that SHOULD have fees updated
err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("usdx", 24000000))) // to get a ratio between 100 - 110% of liquidation ratio we can use 200xrp ($50) and 24 usdx (208% collateralization with liquidation ratio of 200%)
// create CDP for the first address
err := suite.keeper.AddCdp(suite.ctx, addrs[0], c("xrp", 200000000), c("usdx", 24000000))
suite.NoError(err) // check that no error was thrown suite.NoError(err) // check that no error was thrown
// use the other account to create a cdp that SHOULD NOT have fees updated // use the other account to create a cdp that SHOULD NOT have fees updated - 500% collateralization
err = suite.keeper.AddCdp(suite.ctx, addrs[1], cs(c("xrp", 200000000)), cs(c("usdx", 10000000))) // create CDP for the second address
err = suite.keeper.AddCdp(suite.ctx, addrs[1], c("xrp", 200000000), c("usdx", 10000000))
suite.NoError(err) // check that no error was thrown suite.NoError(err) // check that no error was thrown
} }
@ -114,18 +78,19 @@ func (suite *FeeTestSuite) TestUpdateFees() {
suite.NoError(err) // check that we don't have any error suite.NoError(err) // check that we don't have any error
// cdp we expect fees to accumulate for // cdp we expect fees to accumulate for
cdp1, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 1) cdp1, found := suite.keeper.GetCDP(suite.ctx, "xrp", 1)
suite.True(found)
// check fees are not zero // check fees are not zero
// check that the fees have been updated // check that the fees have been updated
suite.False(cdp1.AccumulatedFees.Empty()) suite.False(cdp1.AccumulatedFees.IsZero())
// now check that we have the correct amount of fees overall (22 USDX for this scenario) // now check that we have the correct amount of fees overall (22 USDX for this scenario)
suite.Equal(sdk.NewInt(22), cdp1.AccumulatedFees.AmountOf("usdx")) suite.Equal(sdk.NewInt(22), cdp1.AccumulatedFees.Amount)
suite.Equal(suite.ctx.BlockTime(), cdp1.FeesUpdated) suite.Equal(suite.ctx.BlockTime(), cdp1.FeesUpdated)
// cdp we expect fees to not accumulate for because of rounding to zero // cdp we expect fees to not accumulate for because of rounding to zero
cdp2, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 2) cdp2, found := suite.keeper.GetCDP(suite.ctx, "xrp", 2)
suite.True(found)
// check fees are zero // check fees are zero
suite.True(cdp2.AccumulatedFees.Empty()) suite.True(cdp2.AccumulatedFees.IsZero())
suite.Equal(oldtime, cdp2.FeesUpdated) suite.Equal(oldtime, cdp2.FeesUpdated)
} }

View File

@ -39,7 +39,7 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { 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.NewInt64Coin("usdx", 1000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold, DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
@ -47,7 +47,7 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
{ {
Denom: asset, Denom: asset,
LiquidationRatio: liquidationRatio, LiquidationRatio: liquidationRatio,
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
LiquidationPenalty: d("0.05"), LiquidationPenalty: d("0.05"),
AuctionSize: i(100), AuctionSize: i(100),
@ -56,14 +56,12 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
MarketID: asset + ":usd", MarketID: asset + ":usd",
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParam: cdp.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: i(6),
ConversionFactor: i(6), DebtFloor: i(10000000),
DebtFloor: i(10000000), SavingsRate: d("0.9"),
SavingsRate: d("0.9"),
},
}, },
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
@ -103,7 +101,7 @@ func NewPricefeedGenStateMulti() app.GenesisState {
func NewCDPGenStateMulti() app.GenesisState { 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.NewInt64Coin("usdx", 1000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold, DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
@ -111,7 +109,7 @@ func NewCDPGenStateMulti() app.GenesisState {
{ {
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.NewInt64Coin("usdx", 500000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
LiquidationPenalty: d("0.05"), LiquidationPenalty: d("0.05"),
AuctionSize: i(7000000000), AuctionSize: i(7000000000),
@ -122,7 +120,7 @@ func NewCDPGenStateMulti() app.GenesisState {
{ {
Denom: "btc", Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000000000), sdk.NewInt64Coin("susd", 500000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 500000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
LiquidationPenalty: d("0.025"), LiquidationPenalty: d("0.025"),
AuctionSize: i(10000000), AuctionSize: i(10000000),
@ -131,21 +129,12 @@ func NewCDPGenStateMulti() app.GenesisState {
ConversionFactor: i(8), ConversionFactor: i(8),
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParam: cdp.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: i(6),
ConversionFactor: i(6), DebtFloor: i(10000000),
DebtFloor: i(10000000), SavingsRate: d("0.95"),
SavingsRate: d("0.95"),
},
{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
}, },
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
@ -160,7 +149,7 @@ func NewCDPGenStateMulti() app.GenesisState {
func NewCDPGenStateHighDebtLimit() app.GenesisState { func NewCDPGenStateHighDebtLimit() app.GenesisState {
cdpGenesis := cdp.GenesisState{ cdpGenesis := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000), sdk.NewInt64Coin("susd", 100000000000000)), GlobalDebtLimit: sdk.NewInt64Coin("usdx", 100000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold, DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
@ -168,7 +157,7 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
{ {
Denom: "xrp", Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("2.0"), LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000), sdk.NewInt64Coin("susd", 50000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 50000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
LiquidationPenalty: d("0.05"), LiquidationPenalty: d("0.05"),
AuctionSize: i(7000000000), AuctionSize: i(7000000000),
@ -179,7 +168,7 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
{ {
Denom: "btc", Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000), sdk.NewInt64Coin("susd", 50000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 50000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr
LiquidationPenalty: d("0.025"), LiquidationPenalty: d("0.025"),
AuctionSize: i(10000000), AuctionSize: i(10000000),
@ -188,21 +177,12 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
ConversionFactor: i(8), ConversionFactor: i(8),
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParam: cdp.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: i(6),
ConversionFactor: i(6), DebtFloor: i(10000000),
DebtFloor: i(10000000), SavingsRate: d("0.95"),
SavingsRate: d("0.95"),
},
{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
}, },
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
@ -216,10 +196,10 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState {
func cdps() (cdps cdp.CDPs) { func cdps() (cdps cdp.CDPs) {
_, addrs := app.GeneratePrivKeyAddressPairs(3) _, addrs := app.GeneratePrivKeyAddressPairs(3)
c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(10000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(8000000))), tmtime.Canonical(time.Now())) c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoin("xrp", sdk.NewInt(10000000)), sdk.NewCoin("usdx", sdk.NewInt(8000000)), tmtime.Canonical(time.Now()))
c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(100000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10000000))), tmtime.Canonical(time.Now())) c2 := cdp.NewCDP(uint64(2), addrs[1], sdk.NewCoin("xrp", sdk.NewInt(100000000)), sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoins(sdk.NewCoin("btc", sdk.NewInt(1000000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(10000000))), tmtime.Canonical(time.Now())) c3 := cdp.NewCDP(uint64(3), addrs[1], sdk.NewCoin("btc", sdk.NewInt(1000000000)), sdk.NewCoin("usdx", sdk.NewInt(10000000)), tmtime.Canonical(time.Now()))
c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(1000000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(500000000))), tmtime.Canonical(time.Now())) c4 := cdp.NewCDP(uint64(4), addrs[2], sdk.NewCoin("xrp", sdk.NewInt(1000000000)), sdk.NewCoin("usdx", sdk.NewInt(500000000)), tmtime.Canonical(time.Now()))
cdps = append(cdps, c1, c2, c3, c4) cdps = append(cdps, c1, c2, c3, c4)
return return
} }

View File

@ -14,6 +14,7 @@ import (
// saving the result to a module level variable ensures the compiler doesn't optimize the test away // saving the result to a module level variable ensures the compiler doesn't optimize the test away
var coinsResult sdk.Coins var coinsResult sdk.Coins
var coinResult sdk.Coin
// Note - the iteration benchmarks take a long time to stabilize, to get stable results use: // Note - the iteration benchmarks take a long time to stabilize, to get stable results use:
// go test -benchmem -bench ^(BenchmarkAccountIteration)$ -benchtime 60s -timeout 2h // go test -benchmem -bench ^(BenchmarkAccountIteration)$ -benchtime 60s -timeout 2h
@ -83,7 +84,7 @@ func createCdps(n int) (app.TestApp, sdk.Context, keeper.Keeper) {
) )
cdpKeeper := tApp.GetCDPKeeper() cdpKeeper := tApp.GetCDPKeeper()
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
err := cdpKeeper.AddCdp(ctx, addrs[i], coins[i], cs(c("usdx", 100000000))) err := cdpKeeper.AddCdp(ctx, addrs[i], coins[i][0], c("usdx", 100000000))
if err != nil { if err != nil {
panic("failed to create cdp") panic("failed to create cdp")
} }
@ -106,7 +107,7 @@ func BenchmarkCdpIteration(b *testing.B) {
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
cdpKeeper.IterateAllCdps(ctx, func(c cdp.CDP) (stop bool) { cdpKeeper.IterateAllCdps(ctx, func(c cdp.CDP) (stop bool) {
coinsResult = c.Principal coinResult = c.Principal
return false return false
}) })
} }
@ -135,7 +136,7 @@ func BenchmarkCdpCreation(b *testing.B) {
cdpKeeper := tApp.GetCDPKeeper() cdpKeeper := tApp.GetCDPKeeper()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
err := cdpKeeper.AddCdp(ctx, addrs[i], coins[i], cs(c("usdx", 100000000))) err := cdpKeeper.AddCdp(ctx, addrs[i], coins[i][0], c("usdx", 100000000))
if err != nil { if err != nil {
b.Error("unexpected error") b.Error("unexpected error")
} }

View File

@ -32,11 +32,9 @@ func (k Keeper) GetCollateral(ctx sdk.Context, denom string) (types.CollateralPa
// GetDebtParam returns the debt param with matching denom // GetDebtParam returns the debt param with matching denom
func (k Keeper) GetDebtParam(ctx sdk.Context, denom string) (types.DebtParam, bool) { func (k Keeper) GetDebtParam(ctx sdk.Context, denom string) (types.DebtParam, bool) {
params := k.GetParams(ctx) dp := k.GetParams(ctx).DebtParam
for _, dp := range params.DebtParams { if dp.Denom == denom {
if dp.Denom == denom { return dp, true
return dp, true
}
} }
return types.DebtParam{}, false return types.DebtParam{}, false
} }

View File

@ -42,7 +42,7 @@ func queryGetCdp(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte,
_, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralDenom)
} }
cdp, found := keeper.GetCdpByOwnerAndDenom(ctx, requestParams.Owner, requestParams.CollateralDenom) cdp, found := keeper.GetCdpByOwnerAndDenom(ctx, requestParams.Owner, requestParams.CollateralDenom)
@ -73,7 +73,7 @@ func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]
_, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralDenom)
} }
cdp, found := keeper.GetCdpByOwnerAndDenom(ctx, requestParams.Owner, requestParams.CollateralDenom) cdp, found := keeper.GetCdpByOwnerAndDenom(ctx, requestParams.Owner, requestParams.CollateralDenom)
@ -100,7 +100,7 @@ func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper)
} }
_, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralDenom)
} }
ratio, err := keeper.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, requestParams.CollateralDenom, requestParams.Ratio) ratio, err := keeper.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, requestParams.CollateralDenom, requestParams.Ratio)
@ -133,7 +133,7 @@ func queryGetCdpsByDenom(ctx sdk.Context, req abci.RequestQuery, keeper Keeper)
} }
_, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom)
if !valid { if !valid {
return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) return nil, sdkerrors.Wrap(types.ErrCollateralNotSupported, requestParams.CollateralDenom)
} }
cdps := keeper.GetAllCdpsByDenom(ctx, requestParams.CollateralDenom) cdps := keeper.GetAllCdpsByDenom(ctx, requestParams.CollateralDenom)

View File

@ -93,7 +93,7 @@ func (suite *QuerierTestSuite) SetupTest() {
amount = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 500000000, 5000000000) amount = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 500000000, 5000000000)
debt = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 1000000000, 25000000000) debt = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 1000000000, 25000000000)
} }
err = suite.keeper.AddCdp(suite.ctx, addrs[j], cs(c(collateral, int64(amount))), cs(c("usdx", int64(debt)))) err = suite.keeper.AddCdp(suite.ctx, addrs[j], c(collateral, int64(amount)), c("usdx", int64(debt)))
suite.NoError(err) suite.NoError(err)
c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1)) c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1))
suite.True(f) suite.True(f)
@ -113,7 +113,7 @@ func (suite *QuerierTestSuite) TestQueryCdp() {
ctx := suite.ctx.WithIsCheckTx(false) ctx := suite.ctx.WithIsCheckTx(false)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdp}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdp}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpParams(suite.cdps[0].Owner, suite.cdps[0].Collateral[0].Denom)), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpParams(suite.cdps[0].Owner, suite.cdps[0].Collateral.Denom)),
} }
bz, err := suite.querier(ctx, []string{types.QueryGetCdp}, query) bz, err := suite.querier(ctx, []string{types.QueryGetCdp}, query)
suite.Nil(err) suite.Nil(err)
@ -154,7 +154,7 @@ func (suite *QuerierTestSuite) TestQueryCdpsByDenom() {
ctx := suite.ctx.WithIsCheckTx(false) ctx := suite.ctx.WithIsCheckTx(false)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdps}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdps}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpsParams(suite.cdps[0].Collateral[0].Denom)), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpsParams(suite.cdps[0].Collateral.Denom)),
} }
bz, err := suite.querier(ctx, []string{types.QueryGetCdps}, query) bz, err := suite.querier(ctx, []string{types.QueryGetCdps}, query)
suite.Nil(err) suite.Nil(err)
@ -181,9 +181,9 @@ func (suite *QuerierTestSuite) TestQueryCdpsByRatio() {
expectedBtcIds := []int{} expectedBtcIds := []int{}
for _, cdp := range suite.cdps { for _, cdp := range suite.cdps {
absoluteRatio := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal) absoluteRatio := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, cdp.Collateral, cdp.Principal)
collateralizationRatio, err := suite.keeper.CalculateCollateralizationRatioFromAbsoluteRatio(suite.ctx, cdp.Collateral[0].Denom, absoluteRatio) collateralizationRatio, err := suite.keeper.CalculateCollateralizationRatioFromAbsoluteRatio(suite.ctx, cdp.Collateral.Denom, absoluteRatio)
suite.Nil(err) suite.Nil(err)
if cdp.Collateral[0].Denom == "xrp" { if cdp.Collateral.Denom == "xrp" {
if collateralizationRatio.LT(xrpRatio) { if collateralizationRatio.LT(xrpRatio) {
ratioCountXrp += 1 ratioCountXrp += 1
expectedXrpIds = append(expectedXrpIds, int(cdp.ID)) expectedXrpIds = append(expectedXrpIds, int(cdp.ID))
@ -262,7 +262,7 @@ func (suite *QuerierTestSuite) TestQueryDeposits() {
ctx := suite.ctx.WithIsCheckTx(false) ctx := suite.ctx.WithIsCheckTx(false)
query := abci.RequestQuery{ query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdpDeposits}, "/"), Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetCdpDeposits}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpDeposits(suite.cdps[0].Owner, suite.cdps[0].Collateral[0].Denom)), Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryCdpDeposits(suite.cdps[0].Owner, suite.cdps[0].Collateral.Denom)),
} }
bz, err := suite.querier(ctx, []string{types.QueryGetCdpDeposits}, query) bz, err := suite.querier(ctx, []string{types.QueryGetCdpDeposits}, query)

View File

@ -17,17 +17,11 @@ import (
// (this is the equivalent of saying that fees are no longer accumulated by a cdp once it gets liquidated) // (this is the equivalent of saying that fees are no longer accumulated by a cdp once it gets liquidated)
func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error { func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
// Calculate the previous collateral ratio // Calculate the previous collateral ratio
oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
// Move debt coins from cdp to liquidator account // Move debt coins from cdp to liquidator account
deposits := k.GetDeposits(ctx, cdp.ID) deposits := k.GetDeposits(ctx, cdp.ID)
debt := sdk.ZeroInt() debt := cdp.Principal.Amount.Add(cdp.AccumulatedFees.Amount)
for _, pc := range cdp.Principal {
debt = debt.Add(pc.Amount)
}
for _, dc := range cdp.AccumulatedFees {
debt = debt.Add(dc.Amount)
}
modAccountDebt := k.getModAccountDebt(ctx, types.ModuleName) modAccountDebt := k.getModAccountDebt(ctx, types.ModuleName)
if modAccountDebt.LT(debt) { if modAccountDebt.LT(debt) {
debt = modAccountDebt debt = modAccountDebt
@ -48,29 +42,24 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)), sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)),
), ),
) )
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, dep.Amount) err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(dep.Amount))
if err != nil { if err != nil {
return err return err
} }
k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor) k.DeleteDeposit(ctx, dep.CdpID, dep.Depositor)
} }
err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal[0].Denom) err = k.AuctionCollateral(ctx, deposits, debt, cdp.Principal.Denom)
if err != nil { if err != nil {
return err return err
} }
// Decrement total principal for this collateral type // Decrement total principal for this collateral type
for _, dc := range cdp.Principal { coinsToDecrement := cdp.Principal.Add(cdp.AccumulatedFees)
feeAmount := cdp.AccumulatedFees.AmountOf(dc.Denom) k.DecrementTotalPrincipal(ctx, cdp.Collateral.Denom, coinsToDecrement)
coinsToDecrement := sdk.NewCoins(dc)
if feeAmount.IsPositive() { // Delete CDP from state
feeCoins := sdk.NewCoins(sdk.NewCoin(dc.Denom, feeAmount))
coinsToDecrement = coinsToDecrement.Add(feeCoins...)
}
k.DecrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, coinsToDecrement)
}
k.RemoveCdpOwnerIndex(ctx, cdp) k.RemoveCdpOwnerIndex(ctx, cdp)
k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral.Denom, cdp.ID, oldCollateralToDebtRatio)
k.DeleteCDP(ctx, cdp) k.DeleteCDP(ctx, cdp)
return nil return nil
} }

View File

@ -103,7 +103,7 @@ func (suite *SeizeTestSuite) createCdps() {
tracker.debt += int64(debt) tracker.debt += int64(debt)
} }
} }
err := suite.keeper.AddCdp(suite.ctx, addrs[j], cs(c(collateral, int64(amount))), cs(c("usdx", int64(debt)))) err := suite.keeper.AddCdp(suite.ctx, addrs[j], c(collateral, int64(amount)), c("usdx", int64(debt)))
suite.NoError(err) suite.NoError(err)
c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1)) c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1))
suite.True(f) suite.True(f)
@ -129,37 +129,40 @@ func (suite *SeizeTestSuite) setPrice(price sdk.Dec, market string) {
func (suite *SeizeTestSuite) TestSeizeCollateral() { func (suite *SeizeTestSuite) TestSeizeCollateral() {
suite.createCdps() suite.createCdps()
sk := suite.app.GetSupplyKeeper() sk := suite.app.GetSupplyKeeper()
cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) cdp, found := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
p := cdp.Principal[0].Amount suite.True(found)
cl := cdp.Collateral[0].Amount p := cdp.Principal.Amount
cl := cdp.Collateral.Amount
tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
err := suite.keeper.SeizeCollateral(suite.ctx, cdp) err := suite.keeper.SeizeCollateral(suite.ctx, cdp)
suite.NoError(err) 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)
auctionKeeper := suite.app.GetAuctionKeeper() auctionKeeper := suite.app.GetAuctionKeeper()
_, found := auctionKeeper.GetAuction(suite.ctx, auction.DefaultNextAuctionID) _, found = auctionKeeper.GetAuction(suite.ctx, auction.DefaultNextAuctionID)
suite.True(found) suite.True(found)
auctionMacc := sk.GetModuleAccount(suite.ctx, auction.ModuleName) auctionMacc := sk.GetModuleAccount(suite.ctx, auction.ModuleName)
suite.Equal(cs(c("debt", p.Int64()), c("xrp", cl.Int64())), auctionMacc.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], c("xrp", 10))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
} }
func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() { func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() {
suite.createCdps() suite.createCdps()
sk := suite.app.GetSupplyKeeper() sk := suite.app.GetSupplyKeeper()
cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) cdp, found := 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.True(found)
err := suite.keeper.DepositCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], c("xrp", 6999000000))
suite.NoError(err) suite.NoError(err)
cdp, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) cdp, found = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2))
suite.True(found)
deposits := suite.keeper.GetDeposits(suite.ctx, cdp.ID) deposits := suite.keeper.GetDeposits(suite.ctx, cdp.ID)
suite.Equal(2, len(deposits)) suite.Equal(2, len(deposits))
p := cdp.Principal[0].Amount p := cdp.Principal.Amount
cl := cdp.Collateral[0].Amount cl := cdp.Collateral.Amount
tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")
err = suite.keeper.SeizeCollateral(suite.ctx, cdp) err = suite.keeper.SeizeCollateral(suite.ctx, cdp)
suite.NoError(err) suite.NoError(err)
@ -170,7 +173,7 @@ func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() {
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], c("xrp", 10))
suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) suite.Require().True(errors.Is(err, types.ErrCdpNotFound))
} }
@ -180,7 +183,8 @@ func (suite *SeizeTestSuite) TestLiquidateCdps() {
acc := sk.GetModuleAccount(suite.ctx, types.ModuleName) acc := sk.GetModuleAccount(suite.ctx, types.ModuleName)
originalXrpCollateral := acc.GetCoins().AmountOf("xrp") originalXrpCollateral := acc.GetCoins().AmountOf("xrp")
suite.setPrice(d("0.2"), "xrp:usd") suite.setPrice(d("0.2"), "xrp:usd")
p, _ := suite.keeper.GetCollateral(suite.ctx, "xrp") p, found := suite.keeper.GetCollateral(suite.ctx, "xrp")
suite.True(found)
suite.keeper.LiquidateCdps(suite.ctx, "xrp:usd", "xrp", p.LiquidationRatio) suite.keeper.LiquidateCdps(suite.ctx, "xrp:usd", "xrp", p.LiquidationRatio)
acc = sk.GetModuleAccount(suite.ctx, types.ModuleName) acc = sk.GetModuleAccount(suite.ctx, types.ModuleName)
finalXrpCollateral := acc.GetCoins().AmountOf("xrp") finalXrpCollateral := acc.GetCoins().AmountOf("xrp")

View File

@ -27,7 +27,7 @@ func TestDecodeDistributionStore(t *testing.T) {
cdpIds := []uint64{1, 2, 3, 4, 5} cdpIds := []uint64{1, 2, 3, 4, 5}
denom := "denom" denom := "denom"
oneCoins := sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt())) oneCoins := sdk.NewCoin(denom, sdk.OneInt())
deposit := types.Deposit{CdpID: 1, Amount: oneCoins} deposit := types.Deposit{CdpID: 1, Amount: oneCoins}
principal := sdk.OneInt() principal := sdk.OneInt()
prevDistTime := time.Now().UTC() prevDistTime := time.Now().UTC()

View File

@ -69,7 +69,7 @@ func randomCdpGenState(selection int) types.GenesisState {
case 0: case 0:
return types.GenesisState{ return types.GenesisState{
Params: types.Params{ Params: types.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000)), GlobalDebtLimit: sdk.NewInt64Coin("usdx", 100000000000000),
SurplusAuctionThreshold: types.DefaultSurplusThreshold, SurplusAuctionThreshold: types.DefaultSurplusThreshold,
DebtAuctionThreshold: types.DefaultDebtThreshold, DebtAuctionThreshold: types.DefaultDebtThreshold,
SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency,
@ -77,7 +77,7 @@ func randomCdpGenState(selection int) types.GenesisState {
{ {
Denom: "xrp", Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("2.0"), LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 20000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 20000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000004431822130"), StabilityFee: sdk.MustNewDecFromStr("1.000000004431822130"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.075"), LiquidationPenalty: sdk.MustNewDecFromStr("0.075"),
AuctionSize: sdk.NewInt(100000000000), AuctionSize: sdk.NewInt(100000000000),
@ -88,7 +88,7 @@ func randomCdpGenState(selection int) types.GenesisState {
{ {
Denom: "btc", Denom: "btc",
LiquidationRatio: sdk.MustNewDecFromStr("1.25"), LiquidationRatio: sdk.MustNewDecFromStr("1.25"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 50000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"), LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(1000000000), AuctionSize: sdk.NewInt(1000000000),
@ -99,7 +99,7 @@ func randomCdpGenState(selection int) types.GenesisState {
{ {
Denom: "bnb", Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 30000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 30000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"), StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.15"), LiquidationPenalty: sdk.MustNewDecFromStr("0.15"),
AuctionSize: sdk.NewInt(1000000000000), AuctionSize: sdk.NewInt(1000000000000),
@ -108,14 +108,12 @@ func randomCdpGenState(selection int) types.GenesisState {
ConversionFactor: sdk.NewInt(8), ConversionFactor: sdk.NewInt(8),
}, },
}, },
DebtParams: types.DebtParams{ DebtParam: types.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: sdk.NewInt(6),
ConversionFactor: sdk.NewInt(6), DebtFloor: sdk.NewInt(10000000),
DebtFloor: sdk.NewInt(10000000), SavingsRate: sdk.MustNewDecFromStr("0.95"),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
}, },
}, },
StartingCdpID: types.DefaultCdpStartingID, StartingCdpID: types.DefaultCdpStartingID,
@ -127,7 +125,7 @@ func randomCdpGenState(selection int) types.GenesisState {
case 1: case 1:
return types.GenesisState{ return types.GenesisState{
Params: types.Params{ Params: types.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000)), GlobalDebtLimit: sdk.NewInt64Coin("usdx", 100000000000000),
SurplusAuctionThreshold: types.DefaultSurplusThreshold, SurplusAuctionThreshold: types.DefaultSurplusThreshold,
DebtAuctionThreshold: types.DefaultDebtThreshold, DebtAuctionThreshold: types.DefaultDebtThreshold,
SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency,
@ -135,7 +133,7 @@ func randomCdpGenState(selection int) types.GenesisState {
{ {
Denom: "bnb", Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"), LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 100000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"), StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.075"), LiquidationPenalty: sdk.MustNewDecFromStr("0.075"),
AuctionSize: sdk.NewInt(10000000000), AuctionSize: sdk.NewInt(10000000000),
@ -144,14 +142,12 @@ func randomCdpGenState(selection int) types.GenesisState {
ConversionFactor: sdk.NewInt(8), ConversionFactor: sdk.NewInt(8),
}, },
}, },
DebtParams: types.DebtParams{ DebtParam: types.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: sdk.NewInt(6),
ConversionFactor: sdk.NewInt(6), DebtFloor: sdk.NewInt(10000000),
DebtFloor: sdk.NewInt(10000000), SavingsRate: sdk.MustNewDecFromStr("0.95"),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
}, },
}, },
StartingCdpID: types.DefaultCdpStartingID, StartingCdpID: types.DefaultCdpStartingID,

View File

@ -61,8 +61,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
} }
randCollateralParam := collateralParams[r.Intn(len(collateralParams))] randCollateralParam := collateralParams[r.Intn(len(collateralParams))]
randDebtAsset := randCollateralParam.DebtLimit[r.Intn(len(randCollateralParam.DebtLimit))] debtParam, _ := k.GetDebtParam(ctx, randCollateralParam.DebtLimit.Denom)
randDebtParam, _ := k.GetDebtParam(ctx, randDebtAsset.Denom)
if coins.AmountOf(randCollateralParam.Denom).IsZero() { if coins.AmountOf(randCollateralParam.Denom).IsZero() {
return simulation.NoOpMsg(types.ModuleName), nil, nil return simulation.NoOpMsg(types.ModuleName), nil, nil
} }
@ -72,7 +71,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
return simulation.NoOpMsg(types.ModuleName), nil, err return simulation.NoOpMsg(types.ModuleName), nil, err
} }
// convert the price to the same units as the debt param // convert the price to the same units as the debt param
priceShifted := ShiftDec(price.Price, randDebtParam.ConversionFactor) priceShifted := ShiftDec(price.Price, debtParam.ConversionFactor)
spendableCoins := acc.SpendableCoins(ctx.BlockTime()) spendableCoins := acc.SpendableCoins(ctx.BlockTime())
fees, err := simulation.RandomFees(r, ctx, spendableCoins) fees, err := simulation.RandomFees(r, ctx, spendableCoins)
@ -85,7 +84,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
if !found { if !found {
// calculate the minimum amount of collateral that is needed to create a cdp with the debt floor amount of debt and the minimum liquidation ratio // calculate the minimum amount of collateral that is needed to create a cdp with the debt floor amount of debt and the minimum liquidation ratio
// (debtFloor * liquidationRatio)/priceShifted // (debtFloor * liquidationRatio)/priceShifted
minCollateralDeposit := (sdk.NewDecFromInt(randDebtParam.DebtFloor).Mul(randCollateralParam.LiquidationRatio)).Quo(priceShifted) minCollateralDeposit := (sdk.NewDecFromInt(debtParam.DebtFloor).Mul(randCollateralParam.LiquidationRatio)).Quo(priceShifted)
// convert to proper collateral units // convert to proper collateral units
minCollateralDeposit = ShiftDec(minCollateralDeposit, randCollateralParam.ConversionFactor) minCollateralDeposit = ShiftDec(minCollateralDeposit, randCollateralParam.ConversionFactor)
// convert to integer and always round up // convert to integer and always round up
@ -104,17 +103,17 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
// calculate the max amount of debt that could be drawn for the chosen deposit // calculate the max amount of debt that could be drawn for the chosen deposit
maxDebtDraw := collateralDepositValue.Quo(randCollateralParam.LiquidationRatio).TruncateInt() maxDebtDraw := collateralDepositValue.Quo(randCollateralParam.LiquidationRatio).TruncateInt()
// check that the debt limit hasn't been reached // check that the debt limit hasn't been reached
availableAssetDebt := randCollateralParam.DebtLimit.AmountOf(randDebtParam.Denom).Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, randDebtParam.Denom)) availableAssetDebt := randCollateralParam.DebtLimit.Amount.Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, debtParam.Denom))
if availableAssetDebt.LTE(randDebtParam.DebtFloor) { if availableAssetDebt.LTE(debtParam.DebtFloor) {
// debt limit has been reached // debt limit has been reached
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot open cdp", false, nil), nil, nil return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot open cdp", false, nil), nil, nil
} }
// ensure that the debt draw does not exceed the debt limit // ensure that the debt draw does not exceed the debt limit
maxDebtDraw = sdk.MinInt(maxDebtDraw, availableAssetDebt) maxDebtDraw = sdk.MinInt(maxDebtDraw, availableAssetDebt)
// randomly select a debt draw amount // randomly select a debt draw amount
debtDraw := sdk.NewInt(int64(simulation.RandIntBetween(r, int(randDebtParam.DebtFloor.Int64()), int(maxDebtDraw.Int64())))) debtDraw := sdk.NewInt(int64(simulation.RandIntBetween(r, int(debtParam.DebtFloor.Int64()), int(maxDebtDraw.Int64()))))
msg := types.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, collateralDeposit)), sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, debtDraw))) msg := types.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoin(randCollateralParam.Denom, collateralDeposit), sdk.NewCoin(debtParam.Denom, debtDraw))
tx := helpers.GenTx( tx := helpers.GenTx(
[]sdk.Msg{msg}, []sdk.Msg{msg},
@ -136,9 +135,9 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
// a cdp already exists, deposit to it, draw debt from it, or repay debt to it // a cdp already exists, deposit to it, draw debt from it, or repay debt to it
// close 25% of the time // close 25% of the time
if canClose(acc, existingCDP, randDebtParam.Denom) && shouldClose(r) { if canClose(acc, existingCDP, debtParam.Denom) && shouldClose(r) {
repaymentAmount := spendableCoins.AmountOf(randDebtParam.Denom) repaymentAmount := spendableCoins.AmountOf(debtParam.Denom)
msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, repaymentAmount))) msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoin(debtParam.Denom, repaymentAmount))
tx := helpers.GenTx( tx := helpers.GenTx(
[]sdk.Msg{msg}, []sdk.Msg{msg},
@ -161,7 +160,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
// deposit 25% of the time // deposit 25% of the time
if hasCoins(acc, randCollateralParam.Denom) && shouldDeposit(r) { if hasCoins(acc, randCollateralParam.Denom) && shouldDeposit(r) {
randDepositAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(spendableCoins.AmountOf(randCollateralParam.Denom).Int64())))) randDepositAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(spendableCoins.AmountOf(randCollateralParam.Denom).Int64()))))
msg := types.NewMsgDeposit(acc.GetAddress(), acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, randDepositAmount))) msg := types.NewMsgDeposit(acc.GetAddress(), acc.GetAddress(), sdk.NewCoin(randCollateralParam.Denom, randDepositAmount))
tx := helpers.GenTx( tx := helpers.GenTx(
[]sdk.Msg{msg}, []sdk.Msg{msg},
@ -183,12 +182,12 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
// draw debt 25% of the time // draw debt 25% of the time
if shouldDraw(r) { if shouldDraw(r) {
collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.AmountOf(randCollateralParam.Denom)), randCollateralParam.ConversionFactor.Neg()) collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.Amount), randCollateralParam.ConversionFactor.Neg())
collateralValue := collateralShifted.Mul(priceShifted) collateralValue := collateralShifted.Mul(priceShifted)
newFeesAccumulated := k.CalculateFees(ctx, existingCDP.Principal, sdk.NewInt(ctx.BlockTime().Unix()-existingCDP.FeesUpdated.Unix()), randCollateralParam.Denom).AmountOf(randDebtParam.Denom) newFeesAccumulated := k.CalculateFees(ctx, existingCDP.Principal, sdk.NewInt(ctx.BlockTime().Unix()-existingCDP.FeesUpdated.Unix()), randCollateralParam.Denom).Amount
totalFees := existingCDP.AccumulatedFees.AmountOf(randCollateralParam.Denom).Add(newFeesAccumulated) totalFees := existingCDP.AccumulatedFees.Amount.Add(newFeesAccumulated)
// given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio // given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio
debt := existingCDP.Principal.AmountOf(randDebtParam.Denom).Add(totalFees) debt := existingCDP.Principal.Amount.Add(totalFees)
maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio) maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio)
maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt() maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt()
if maxDebt.LTE(sdk.OneInt()) { if maxDebt.LTE(sdk.OneInt()) {
@ -196,7 +195,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil
} }
// check if the debt limit has been reached // check if the debt limit has been reached
availableAssetDebt := randCollateralParam.DebtLimit.AmountOf(randDebtParam.Denom).Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, randDebtParam.Denom)) availableAssetDebt := randCollateralParam.DebtLimit.Amount.Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, debtParam.Denom))
if availableAssetDebt.LTE(sdk.OneInt()) { if availableAssetDebt.LTE(sdk.OneInt()) {
// debt limit has been reached // debt limit has been reached
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot draw more debt", false, nil), nil, nil return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot draw more debt", false, nil), nil, nil
@ -204,7 +203,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
maxDraw := sdk.MinInt(maxDebt, availableAssetDebt) maxDraw := sdk.MinInt(maxDebt, availableAssetDebt)
randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDraw.Int64())))) randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDraw.Int64()))))
msg := types.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randDrawAmount))) msg := types.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoin(debtParam.Denom, randDrawAmount))
tx := helpers.GenTx( tx := helpers.GenTx(
[]sdk.Msg{msg}, []sdk.Msg{msg},
@ -225,19 +224,19 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedK
} }
// repay debt 25% of the time // repay debt 25% of the time
if hasCoins(acc, randDebtParam.Denom) { if hasCoins(acc, debtParam.Denom) {
debt := existingCDP.Principal.AmountOf(randDebtParam.Denom) debt := existingCDP.Principal.Amount
maxRepay := spendableCoins.AmountOf(randDebtParam.Denom) maxRepay := spendableCoins.AmountOf(debtParam.Denom)
payableDebt := debt.Sub(randDebtParam.DebtFloor) payableDebt := debt.Sub(debtParam.DebtFloor)
if maxRepay.GT(payableDebt) { if maxRepay.GT(payableDebt) {
maxRepay = payableDebt maxRepay = payableDebt
} }
randRepayAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxRepay.Int64())))) randRepayAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxRepay.Int64()))))
if debt.Equal(randDebtParam.DebtFloor) && spendableCoins.AmountOf(randDebtParam.Denom).GTE(debt) { if debt.Equal(debtParam.DebtFloor) && spendableCoins.AmountOf(debtParam.Denom).GTE(debt) {
randRepayAmount = debt randRepayAmount = debt
} }
msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randRepayAmount))) msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoin(debtParam.Denom, randRepayAmount))
tx := helpers.GenTx( tx := helpers.GenTx(
[]sdk.Msg{msg}, []sdk.Msg{msg},
@ -284,6 +283,6 @@ func shouldClose(r *rand.Rand) bool {
} }
func canClose(acc authexported.Account, c types.CDP, denom string) bool { func canClose(acc authexported.Account, c types.CDP, denom string) bool {
repaymentAmount := c.Principal.Add(c.AccumulatedFees...).AmountOf(denom) repaymentAmount := c.Principal.Add(c.AccumulatedFees).Amount
return acc.GetCoins().AmountOf(denom).GTE(repaymentAmount) return acc.GetCoins().AmountOf(denom).GTE(repaymentAmount)
} }

View File

@ -4,17 +4,30 @@
CDPs enable the creation of a stable asset by collateralization with another on chain asset. CDPs enable the creation of a stable asset by collateralization with another on chain asset.
A CDP is scoped to one collateral type. It has one primary owner, and a set of "depositors". The depositors can deposit and withdraw collateral to the CDP. The owner can draw stable assets (creating debt) and repay them to cancel the debt. A CDP is scoped to one collateral type. It has one primary owner, and a set of "depositors". The depositors can deposit and withdraw collateral to the CDP. The owner can draw stable assets (creating debt), deposit and withdraw collateral, and repay stable assets to cancel the debt.
Once created stable assets are free to be transferred between users, but a CDP owner must repay their debt to get their collateral back. Once created, stable assets are free to be transferred between users, but a CDP owner must repay their debt to get their collateral back.
User interactions with this module: User interactions with this module:
- create a new cdp by depositing some type of coin as collateral - create a new CDP by depositing a supported coin as collateral and minting debt
- withdraw newly minted stable coin from this CDP (up to a fraction of the value of the collateral) - deposit to a CDP controlled a different owner address
- withdraw deposited collateral, if it doesn't put the CDP below the liquidation ratio
- issue stable coins from this CDP (up to a fraction of the value of the collateral)
- repay debt by paying back stable coins (including paying any fees accrued) - repay debt by paying back stable coins (including paying any fees accrued)
- remove collateral and close CDP - remove collateral and close CDP
Module interactions:
- fees for all CDPs are updated each block
- the value of fees (surplus) is divded between users, via the savings rate, and owners of the governance token, via burning governance tokens proportional to surplus
- the value of an asset that is supported for CDPs is determined by querying an external pricefeed
- if the price of an asset puts a CDP below the liquidation ratio, the CDP is liquidated
- liquidated collateral is divided into lots and sent to an external auction module
- collateral that is returned from the auction module is returned to the account that deposited that collateral
- if auctions do not recover the desired amount of debt, debt auctions are triggered after a certain threshold of global debt is reached
- surplus auctions are triggered after a certain threshold of surplus is triggered
## Liquidation & Stability System ## Liquidation & Stability System
In the event of a decrease in the price of the collateral, the total value of all collateral in CDPs may drop below the value of all the issued stable assets. This undesirable event is countered through two mechanisms: In the event of a decrease in the price of the collateral, the total value of all collateral in CDPs may drop below the value of all the issued stable assets. This undesirable event is countered through two mechanisms:
@ -35,13 +48,13 @@ The cdp module uses two module accounts - one to hold debt coins associated with
When a user repays stable asset withdrawn from a CDP, they must also pay a fee. When a user repays stable asset withdrawn from a CDP, they must also pay a fee.
This is calculated according to the amount of stable asset withdrawn and the time withdrawn for. Like interest on a loan fees grow at a compounding percentage of original debt. This is calculated according to the amount of stable asset withdrawn and the time withdrawn for. Like interest on a loan, fees grow at a compounding percentage of original debt.
Fees create incentives to open or close CDPs and can be changed by governance to help keep the system functioning through changing market conditions. Fees create incentives to open or close CDPs and can be changed by governance to help keep the system functioning through changing market conditions.
A further fee is applied on liquidation of a CDP. Normally when the collateral is sold to cover the debt, any excess not sold is returned to the CDP holder. The liquidation fee reduces the amount of excess collateral returned, representing a cut that the system takes. A further fee is applied on liquidation of a CDP. Normally when the collateral is sold to cover the debt, any excess not sold is returned to the CDP holder. The liquidation fee reduces the amount of excess collateral returned, representing a cut that the system takes.
Fees accumulate to the system before being automatically sold at auction for governance token. These are then burned, acting as incentive for safe governance of the system. Fees accumulate to the system and are split between the savings rate and surplus. Fees accumulated by the savings rate are distributed directly to holders of stable coins at a specified frequency. Savings rate distributions are proportional to tokens held. For example, if an account holds 1% of all stable coins, they will receive 1% of the savings rate distribution. Fees accumulated as surplus are automatically sold at auction for governance token once a certain threshold is reached. The governance tokens raised at auction are then burned, acting as incentive for safe governance of the system.
## Governance ## Governance
@ -52,6 +65,7 @@ Governance is important for actions such as:
- enabling CDPs to be created with new collateral assets - enabling CDPs to be created with new collateral assets
- changing fee rates to incentivize behavior - changing fee rates to incentivize behavior
- increasing the debt ceiling to allow more stable asset to be created - increasing the debt ceiling to allow more stable asset to be created
- increasing/decreasing the savings rate to promote stability of the debt asset
## Dependency: supply ## Dependency: supply

View File

@ -22,16 +22,17 @@ The CDP's collateral always equal to the total of the deposits.
type CDP struct { type CDP struct {
ID uint64 ID uint64
Owner sdk.AccAddress Owner sdk.AccAddress
Collateral sdk.Coins Collateral sdk.Coin
Principal sdk.Coins Principal sdk.Coin
AccumulatedFees sdk.Coins AccumulatedFees sdk.Coin
FeesUpdated time.Time FeesUpdated time.Time
} }
``` ```
CDPs are stored with a couple of database indexes for faster lookup: CDPs are stored with three database indexes for faster lookup:
- by collateral ratio - to look up cdps that are close to the liquidation ratio - by collateral ratio - to look up cdps that are close to the liquidation ratio
- by collateral denom - to look up cdps with a particular collateral asset
- by owner index - to look up cdps that an address is the owner of - by owner index - to look up cdps that an address is the owner of
## Deposit ## Deposit
@ -42,7 +43,7 @@ A Deposit is a struct recording collateral added to a CDP by one address. The ad
type Deposit struct { type Deposit struct {
CdpID uint64 CdpID uint64
Depositor sdk.AccAddress Depositor sdk.AccAddress
Amount sdk.Coins Amount sdk.Coin
} }
``` ```
@ -58,10 +59,14 @@ A global counter used to create unique CDP ids.
The name of the internal debt coin. Its value can be configured at genesis. The name of the internal debt coin. Its value can be configured at genesis.
## GovDenom
The name of the internal governance coin. Its value can be configured at genesis.
## Total Principle ## Total Principle
Sum of all non seized debt plus accumulated fees. This is used to calculate the new debt created every block due to the fee interest rate. Sum of all non seized debt plus accumulated fees.
## Previous Block Time ## Previous Savings Distribution Time
A record of the last block time used to calculate fees. A record of the last block time when the savings rate was distributed

View File

@ -9,8 +9,8 @@ CreateCDP sets up and stores a new CDP, adding collateral from the sender, and d
```go ```go
type MsgCreateCDP struct { type MsgCreateCDP struct {
Sender sdk.AccAddress Sender sdk.AccAddress
Collateral sdk.Coins Collateral sdk.Coin
Principal sdk.Coins Principal sdk.Coin
} }
``` ```
@ -29,7 +29,7 @@ Deposit adds collateral to a CDP in the form of a deposit. Collateral is taken f
type MsgDeposit struct { type MsgDeposit struct {
Owner sdk.AccAddress Owner sdk.AccAddress
Depositor sdk.AccAddress Depositor sdk.AccAddress
Collateral sdk.Coins Collateral sdk.Coin
} }
``` ```
@ -47,15 +47,14 @@ Withdraw removes collateral from a CDP, provided it would not put the CDP under
type MsgWithdraw struct { type MsgWithdraw struct {
Owner sdk.AccAddress Owner sdk.AccAddress
Depositor sdk.AccAddress Depositor sdk.AccAddress
Collateral sdk.Coins Collateral sdk.Coin
} }
``` ```
State Changes: State Changes:
- `Collateral` coins are sent from the cdp module account to `Depositor` - `Collateral` coins are sent from the cdp module account to `Depositor`
- `Collateral` amount of coins subtracted from the `Deposit` struct - `Collateral` amount of coins subtracted from the `Deposit` struct. If the amount is now zero, the struct is deleted
- cdp fees are updated (see below)
## DrawDebt ## DrawDebt
@ -65,7 +64,7 @@ DrawDebt creates debt in a CDP, minting new stable asset which is sent to the se
type MsgDrawDebt struct { type MsgDrawDebt struct {
Sender sdk.AccAddress Sender sdk.AccAddress
CdpDenom string CdpDenom string
Principal sdk.Coins Principal sdk.Coin
} }
``` ```
@ -74,7 +73,6 @@ State Changes:
- mint `Principal` coins and send them to `Sender`, updating the CDP's `Principal` field - mint `Principal` coins and send them to `Sender`, updating the CDP's `Principal` field
- mint equal amount of internal debt coins and store in the module account - mint equal amount of internal debt coins and store in the module account
- increment total principal for principal denom - increment total principal for principal denom
- cdp fees are updated (see below)
## RepayDebt ## RepayDebt
@ -84,7 +82,7 @@ RepayDebt removes some debt from a CDP and burns the corresponding amount of sta
type MsgRepayDebt struct { type MsgRepayDebt struct {
Sender sdk.AccAddress Sender sdk.AccAddress
CdpDenom string CdpDenom string
Payment sdk.Coins Payment sdk.Coin
} }
``` ```
@ -93,13 +91,12 @@ State Changes:
- burn `Payment` coins taken from `Sender`, updating the CDP by reducing `Principal` field by `Paymment` - burn `Payment` coins taken from `Sender`, updating the CDP by reducing `Principal` field by `Paymment`
- burn an equal amount of internal debt coins - burn an equal amount of internal debt coins
- decrement total principal for payment denom - decrement total principal for payment denom
- cdp fees are updated (see below) - if fees and principal are zero, return collateral to depositors and delete the CDP struct:
- if fees and principal are zero, return collateral to depositors:
- For each deposit, send coins from the cdp module account to the depositor, and delete the deposit struct from store. - For each deposit, send coins from the cdp module account to the depositor, and delete the deposit struct from store.
## Fees ## Fees
When CDPs are updated by the above messages the fees accumulated since the last update are calculated and added on. At the beginning of each block, fees accumulated since the last update are calculated and added on.
``` ```
feesAccumulated = (outstandingDebt * (feeRate^periods)) - outstandingDebt feesAccumulated = (outstandingDebt * (feeRate^periods)) - outstandingDebt
@ -111,6 +108,10 @@ where:
- `periods` is the number of seconds since last fee update - `periods` is the number of seconds since last fee update
- `feeRate` is the per second debt interest rate - `feeRate` is the per second debt interest rate
Fees are divided between surplus and savings rate. For example, if the savings rate is 0.95, 95% of all fees go towards the savings rate and 5% go to surplus.
In the event that the rounded value of `feesAccumulated` is zero, fees are not updated, and the `FeesUpdated` value on the CDP struct is not updated. When a sufficient number of periods have passed such that the rounded value is no longer zero, fees will be updated.
## Database Indexes ## Database Indexes
When CDPs are update by the above messages the database indexes are also updated. When CDPs are update by the above messages the database indexes are also updated.

View File

@ -2,30 +2,26 @@
At the start of every block the BeginBlocker of the cdp module: At the start of every block the BeginBlocker of the cdp module:
- updates total CDP fees - updates fees for CDPs
- update fees for individual "risky" CDPs
- liquidates CDPs under the collateral ratio - liquidates CDPs under the collateral ratio
- nets out system debt and, if necessary, starts auctions to re-balance it - nets out system debt and, if necessary, starts auctions to re-balance it
- records the last block time - pays out the savings rate if sufficient time has past
- records the last savings rate distribution, if one occurred
## Update Fees ## Update Fees
- The total fees accumulated since the last block across all CDPs are calculated. - The total fees accumulated since the last block for each CDP are calculated.
- An equal amount of debt coins are minted and sent to the system's CDP module account. - If the fee amount is non-zero:
- An equal amount of stable asset coins are minted and sent to the system's liquidator module account - Set the updated value for fees
- Set the fees updated time for the CDP to the current block time
## Update risky cdps - An equal amount of debt coins are minted and sent to the system's CDP module account.
- An equal amount of stable asset coins are minted and sent to the system's liquidator module account
- UpdateFeesForRiskyCdps calculates fees for risky CDPs - Increment total principal.
- Select the CDPs with 10% of the liquidation ratio - the risky CDPs
- Calculate additional accumulated fees on each of those CDPs
- Update the fees updated time for the CDP to the current block time
## Liquidate CDP ## Liquidate CDP
- Get every cdp that is under the liquidation ratio for its collateral type. - Get every cdp that is under the liquidation ratio for its collateral type.
- For each cdp: - For each cdp:
- Calculate and update fees since last update.
- Remove all collateral and internal debt coins from cdp and deposits and delete it. Send the coins to the liquidator module account. - Remove all collateral and internal debt coins from cdp and deposits and delete it. Send the coins to the liquidator module account.
- Start auctions of a fixed size from this collateral (with any remainder in a smaller sized auction), sending collateral and debt coins to the auction module account. - Start auctions of a fixed size from this collateral (with any remainder in a smaller sized auction), sending collateral and debt coins to the auction module account.
- Decrement total principal. - Decrement total principal.
@ -42,7 +38,3 @@ At the start of every block the BeginBlocker of the cdp module:
- If `SavingsDistributionFrequency` seconds have elapsed since the previous distribution, the savings rate is applied to all accounts that hold stable asset. - If `SavingsDistributionFrequency` seconds have elapsed since the previous distribution, the savings rate is applied to all accounts that hold stable asset.
- Each account that holds stable asset is distributed a ratable portion of the surplus that is apportioned to the savings rate. - Each account that holds stable asset is distributed a ratable portion of the surplus that is apportioned to the savings rate.
- If distribution occurred, the time of the distribution is recorded. - If distribution occurred, the time of the distribution is recorded.
## Update Previous Block Time
The current block time is recorded.

View File

@ -3,26 +3,26 @@
The cdp module contains the following parameters: The cdp module contains the following parameters:
| Key | Type | Example | Description | | Key | Type | Example | Description |
|------------------ |-------------------------|------------------------------------|------------------------------------------------------------------| |------------------------------|-------------------------|------------------------------------|------------------------------------------------------------------|
| CollateralParams | array (CollateralParam) | [{see below}] | array of params for each enabled collateral type | | CollateralParams | array (CollateralParam) | [{see below}] | array of params for each enabled collateral type |
| DebtParams | array (DebtParam) | [{see below}] | array of params for each enabled pegged asset | | DebtParams | DebtParam | {see below} | array of params for each enabled pegged asset |
| GlobalDebtLimit | array (coin) | [{"denom":"usdx","amount":"1000"}] | maximum pegged assets that can be minted across the whole system | | GlobalDebtLimit | coin | {"denom":"usdx","amount":"1000"} | maximum pegged assets that can be minted across the whole system |
| SavingsDistributionFrequency | string (int) | "84600" | number of seconds between distribution of the savings rate| | SavingsDistributionFrequency | string (int) | "84600" | number of seconds between distribution of the savings rate |
| CircuitBreaker | bool | false | flag to disable user interactions with the system | | CircuitBreaker | bool | false | flag to disable user interactions with the system |
Each CollateralParam has the following parameters: Each CollateralParam has the following parameters:
| Key | Type | Example | Description | | Key | Type | Example | Description |
|------------------|---------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------| |------------------|---------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| Denom | string | "pbnb" | collateral coin denom | | Denom | string | "bnb" | collateral coin denom |
| LiquidationRatio | string (dec) | "1.500000000000000000" | the ratio under which a cdp with this collateral type will be liquidated | | LiquidationRatio | string (dec) | "1.500000000000000000" | the ratio under which a cdp with this collateral type will be liquidated |
| DebtLimit | array (coin) | [{"denom":"pbnb","amount":"1000000000000"}] | maximum pegged asset that can be minted backed by this collateral type | | DebtLimit | coin | {"denom":"bnb","amount":"1000000000000"} | maximum pegged asset that can be minted backed by this collateral type |
| StabilityFee | string (dec) | "1.000000001547126" | per second fee | | StabilityFee | string (dec) | "1.000000001547126" | per second fee |
| Prefix | number (byte) | 34 | identifier used in store keys - **must** be unique across collateral types | | Prefix | number (byte) | 34 | identifier used in store keys - **must** be unique across collateral types |
| MarketID | string | "BNB/USD" | price feed identifier for this collateral type | | MarketID | string | "bnb:usd" | price feed identifier for this collateral type |
| ConversionFactor | string (int) | "6" | 10^_ multiplier to go from external amount (say BTC1.50) to internal representation of that amount (150000000) | | ConversionFactor | string (int) | "6" | 10^_ multiplier to go from external amount (say BTC1.50) to internal representation of that amount (150000000) |
Each DebtParam has the following parameters: DebtParam has the following parameters:
| Key | Type | Example | Description | | Key | Type | Example | Description |
|------------------|--------------|------------|------------------------------------------------------------------------------------------------------------| |------------------|--------------|------------|------------------------------------------------------------------------------------------------------------|
@ -30,3 +30,4 @@ Each DebtParam has the following parameters:
| ReferenceAsset | string | "USD" | asset this asset is pegged to, informational purposes only | | ReferenceAsset | string | "USD" | asset this asset is pegged to, informational purposes only |
| ConversionFactor | string (int) | "6" | 10^_ multiplier to go from external amount (say $1.50) to internal representation of that amount (1500000) | | ConversionFactor | string (int) | "6" | 10^_ multiplier to go from external amount (say $1.50) to internal representation of that amount (1500000) |
| DebtFloor | string (int) | "10000000" | minimum amount of debt that a CDP can contain | | DebtFloor | string (int) | "10000000" | minimum amount of debt that a CDP can contain |
| SavingsRate | string (dec) | "0.95" | the percentage of accumulated fees that go towards the savings rate |

View File

@ -16,4 +16,4 @@ The `x/cdp` module stores and manages Collateralized Debt Positions (or CDPs).
A CDP enables the creation of a stable asset pegged to an external price (usually US Dollar) by collateralization with another asset. Collateral is locked in a CDP and new stable asset can be minted up to some fraction of the value of the collateral. To unlock the collateral, the debt must be repaid by returning some stable asset to the CDP at which point it will be burned and the collateral unlocked. A CDP enables the creation of a stable asset pegged to an external price (usually US Dollar) by collateralization with another asset. Collateral is locked in a CDP and new stable asset can be minted up to some fraction of the value of the collateral. To unlock the collateral, the debt must be repaid by returning some stable asset to the CDP at which point it will be burned and the collateral unlocked.
Pegged assets remain fully collateralized by the value locked in CDPs. In the event of price changes, this collateral can be seized and sold off by the system to reclaim and reduce the supply of stable assets. Pegged assets remain fully collateralized by the value locked in CDPs. In the event of price changes, this collateral can be seized and sold off in auctions by the system to reclaim and reduce the supply of stable assets.

View File

@ -12,15 +12,15 @@ import (
type CDP struct { type CDP struct {
ID uint64 `json:"id" yaml:"id"` // unique id for cdp ID uint64 `json:"id" yaml:"id"` // unique id for cdp
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
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` // Amount of collateral stored in this CDP Collateral sdk.Coin `json:"collateral" yaml:"collateral"` // Amount of collateral stored in this CDP
Principal sdk.Coins `json:"principal" yaml:"principal"` Principal sdk.Coin `json:"principal" yaml:"principal"`
AccumulatedFees sdk.Coins `json:"accumulated_fees" yaml:"accumulated_fees"` AccumulatedFees sdk.Coin `json:"accumulated_fees" yaml:"accumulated_fees"`
FeesUpdated time.Time `json:"fees_updated" yaml:"fees_updated"` // Amount of stable coin drawn from this CDP FeesUpdated time.Time `json:"fees_updated" yaml:"fees_updated"` // Amount of stable coin drawn from this CDP
} }
// NewCDP creates a new CDP object // NewCDP creates a new CDP object
func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coins, principal sdk.Coins, time time.Time) CDP { func NewCDP(id uint64, owner sdk.AccAddress, collateral sdk.Coin, principal sdk.Coin, time time.Time) CDP {
var fees sdk.Coins fees := sdk.NewCoin(principal.Denom, sdk.ZeroInt())
return CDP{ return CDP{
ID: id, ID: id,
Owner: owner, Owner: owner,
@ -43,7 +43,7 @@ func (cdp CDP) String() string {
Fees Last Updated: %s`, Fees Last Updated: %s`,
cdp.Owner, cdp.Owner,
cdp.ID, cdp.ID,
cdp.Collateral[0].Denom, cdp.Collateral.Denom,
cdp.Collateral, cdp.Collateral,
cdp.Principal, cdp.Principal,
cdp.AccumulatedFees, cdp.AccumulatedFees,
@ -101,7 +101,7 @@ func (augCDP AugmentedCDP) String() string {
Collateralization ratio: %s`, Collateralization ratio: %s`,
augCDP.Owner, augCDP.Owner,
augCDP.ID, augCDP.ID,
augCDP.Collateral[0].Denom, augCDP.Collateral.Denom,
augCDP.Collateral, augCDP.Collateral,
augCDP.CollateralValue, augCDP.CollateralValue,
augCDP.Principal, augCDP.Principal,

View File

@ -10,11 +10,11 @@ import (
type Deposit struct { 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.Coin `json:"amount" yaml:"amount"` // Deposit amount
} }
// 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.Coin) Deposit {
return Deposit{cdpID, depositor, amount} return Deposit{cdpID, depositor, amount}
} }
@ -56,7 +56,7 @@ func (ds Deposits) SumCollateral() (sum sdk.Int) {
sum = sdk.ZeroInt() sum = sdk.ZeroInt()
for _, d := range ds { for _, d := range ds {
if !d.Amount.IsZero() { if !d.Amount.IsZero() {
sum = sum.Add(d.Amount[0].Amount) sum = sum.Add(d.Amount.Amount)
} }
} }
return return

View File

@ -25,18 +25,20 @@ var (
ErrDepositNotFound = sdkerrors.Register(ModuleName, 9, "deposit not found") ErrDepositNotFound = sdkerrors.Register(ModuleName, 9, "deposit not found")
// ErrInvalidDeposit error for invalid deposit // ErrInvalidDeposit error for invalid deposit
ErrInvalidDeposit = sdkerrors.Register(ModuleName, 10, "invalid deposit") ErrInvalidDeposit = sdkerrors.Register(ModuleName, 10, "invalid deposit")
// ErrInvalidCollateral error for invalid collateral
ErrInvalidCollateral = sdkerrors.Register(ModuleName, 11, "collateral not supported")
// ErrInvalidPayment error for invalid payment // ErrInvalidPayment error for invalid payment
ErrInvalidPayment = sdkerrors.Register(ModuleName, 12, "invalid payment") ErrInvalidPayment = sdkerrors.Register(ModuleName, 11, "invalid payment")
//ErrDepositNotAvailable error for withdrawing deposits in liquidation //ErrDepositNotAvailable error for withdrawing deposits in liquidation
ErrDepositNotAvailable = sdkerrors.Register(ModuleName, 13, "deposit in liquidation") ErrDepositNotAvailable = sdkerrors.Register(ModuleName, 12, "deposit in liquidation")
// ErrInvalidWithdrawAmount error for invalid withdrawal amount // ErrInvalidWithdrawAmount error for invalid withdrawal amount
ErrInvalidWithdrawAmount = sdkerrors.Register(ModuleName, 14, "withdrawal amount exceeds deposit") ErrInvalidWithdrawAmount = sdkerrors.Register(ModuleName, 13, "withdrawal amount exceeds deposit")
//ErrCdpNotAvailable error for depositing to a CDP in liquidation //ErrCdpNotAvailable error for depositing to a CDP in liquidation
ErrCdpNotAvailable = sdkerrors.Register(ModuleName, 15, "cannot modify cdp in liquidation") ErrCdpNotAvailable = sdkerrors.Register(ModuleName, 14, "cannot modify cdp in liquidation")
// ErrBelowDebtFloor error for creating a cdp with debt below the minimum // ErrBelowDebtFloor error for creating a cdp with debt below the minimum
ErrBelowDebtFloor = sdkerrors.Register(ModuleName, 16, "proposed cdp debt is below minimum") ErrBelowDebtFloor = sdkerrors.Register(ModuleName, 15, "proposed cdp debt is below minimum")
// ErrLoadingAugmentedCDP error loading augmented cdp // ErrLoadingAugmentedCDP error loading augmented cdp
ErrLoadingAugmentedCDP = sdkerrors.Register(ModuleName, 17, "augmented cdp could not be loaded from cdp") ErrLoadingAugmentedCDP = sdkerrors.Register(ModuleName, 16, "augmented cdp could not be loaded from cdp")
// ErrInvalidDebtRequest error for invalid principal input length
ErrInvalidDebtRequest = sdkerrors.Register(ModuleName, 17, "only one principal type per cdp")
// ErrDenomPrefixNotFound error for denom prefix not found
ErrDenomPrefixNotFound = sdkerrors.Register(ModuleName, 18, "denom prefix not found")
) )

View File

@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"time" "time"
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.
@ -55,9 +57,12 @@ func (gs GenesisState) Validate() error {
return fmt.Errorf("previous distribution time not set") return fmt.Errorf("previous distribution time not set")
} }
if gs.DebtDenom == "" { if err := sdk.ValidateDenom(gs.DebtDenom); err != nil {
return fmt.Errorf("debt denom not set") return fmt.Errorf(fmt.Sprintf("debt denom invalid: %v", err))
}
if err := sdk.ValidateDenom(gs.GovDenom); err != nil {
return fmt.Errorf(fmt.Sprintf("gov denom invalid: %v", err))
} }
return nil return nil

View File

@ -21,12 +21,12 @@ var (
// MsgCreateCDP creates a cdp // MsgCreateCDP creates a cdp
type MsgCreateCDP struct { type MsgCreateCDP struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` Collateral sdk.Coin `json:"collateral" yaml:"collateral"`
Principal sdk.Coins `json:"principal" yaml:"principal"` Principal sdk.Coin `json:"principal" yaml:"principal"`
} }
// NewMsgCreateCDP returns a new MsgPlaceBid. // NewMsgCreateCDP returns a new MsgPlaceBid.
func NewMsgCreateCDP(sender sdk.AccAddress, collateral sdk.Coins, principal sdk.Coins) MsgCreateCDP { func NewMsgCreateCDP(sender sdk.AccAddress, collateral sdk.Coin, principal sdk.Coin) MsgCreateCDP {
return MsgCreateCDP{ return MsgCreateCDP{
Sender: sender, Sender: sender,
Collateral: collateral, Collateral: collateral,
@ -45,13 +45,10 @@ func (msg MsgCreateCDP) ValidateBasic() error {
if msg.Sender.Empty() { if msg.Sender.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
} }
if msg.Collateral.Len() != 1 { if msg.Collateral.IsZero() || !msg.Collateral.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "cdps do not support multiple collateral types: %s", msg.Collateral)
}
if !msg.Collateral.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral) return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral)
} }
if msg.Principal.Empty() || !msg.Principal.IsValid() { if msg.Principal.IsZero() || !msg.Principal.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal amount %s", msg.Principal) return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal amount %s", msg.Principal)
} }
return nil return nil
@ -81,11 +78,11 @@ func (msg MsgCreateCDP) String() string {
type MsgDeposit struct { type MsgDeposit struct {
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` Collateral sdk.Coin `json:"collateral" yaml:"collateral"`
} }
// NewMsgDeposit returns a new MsgDeposit // NewMsgDeposit returns a new MsgDeposit
func NewMsgDeposit(owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) MsgDeposit { func NewMsgDeposit(owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coin) MsgDeposit {
return MsgDeposit{ return MsgDeposit{
Owner: owner, Owner: owner,
Depositor: depositor, Depositor: depositor,
@ -107,10 +104,7 @@ func (msg MsgDeposit) ValidateBasic() error {
if msg.Depositor.Empty() { if msg.Depositor.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
} }
if msg.Collateral.Len() != 1 { if !msg.Collateral.IsValid() || msg.Collateral.IsZero() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "cdps do not support multiple collateral types: %s", msg.Collateral)
}
if !msg.Collateral.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral) return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral)
} }
return nil return nil
@ -140,11 +134,11 @@ func (msg MsgDeposit) String() string {
type MsgWithdraw struct { type MsgWithdraw struct {
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"` Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
Owner sdk.AccAddress `json:"owner" yaml:"owner"` Owner sdk.AccAddress `json:"owner" yaml:"owner"`
Collateral sdk.Coins `json:"collateral" yaml:"collateral"` Collateral sdk.Coin `json:"collateral" yaml:"collateral"`
} }
// NewMsgWithdraw returns a new MsgDeposit // NewMsgWithdraw returns a new MsgDeposit
func NewMsgWithdraw(owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) MsgWithdraw { func NewMsgWithdraw(owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coin) MsgWithdraw {
return MsgWithdraw{ return MsgWithdraw{
Owner: owner, Owner: owner,
Depositor: depositor, Depositor: depositor,
@ -166,10 +160,7 @@ func (msg MsgWithdraw) ValidateBasic() error {
if msg.Depositor.Empty() { if msg.Depositor.Empty() {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty")
} }
if msg.Collateral.Len() != 1 { if !msg.Collateral.IsValid() || msg.Collateral.IsZero() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "cdps do not support multiple collateral types: %s", msg.Collateral)
}
if !msg.Collateral.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral) return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral)
} }
return nil return nil
@ -195,15 +186,15 @@ func (msg MsgWithdraw) String() string {
`, msg.Owner, msg.Depositor, msg.Collateral) `, msg.Owner, msg.Depositor, msg.Collateral)
} }
// MsgDrawDebt draw coins off of collateral in cdp // MsgDrawDebt draw debt off of collateral in cdp
type MsgDrawDebt struct { type MsgDrawDebt struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
CdpDenom string `json:"cdp_denom" yaml:"cdp_denom"` CdpDenom string `json:"cdp_denom" yaml:"cdp_denom"`
Principal sdk.Coins `json:"principal" yaml:"principal"` Principal sdk.Coin `json:"principal" yaml:"principal"`
} }
// NewMsgDrawDebt returns a new MsgDrawDebt // NewMsgDrawDebt returns a new MsgDrawDebt
func NewMsgDrawDebt(sender sdk.AccAddress, denom string, principal sdk.Coins) MsgDrawDebt { func NewMsgDrawDebt(sender sdk.AccAddress, denom string, principal sdk.Coin) MsgDrawDebt {
return MsgDrawDebt{ return MsgDrawDebt{
Sender: sender, Sender: sender,
CdpDenom: denom, CdpDenom: denom,
@ -225,7 +216,7 @@ func (msg MsgDrawDebt) ValidateBasic() error {
if strings.TrimSpace(msg.CdpDenom) == "" { if strings.TrimSpace(msg.CdpDenom) == "" {
return errors.New("cdp denom cannot be blank") return errors.New("cdp denom cannot be blank")
} }
if msg.Principal.Empty() || !msg.Principal.IsValid() { if msg.Principal.IsZero() || !msg.Principal.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal amount %s", msg.Principal) return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal amount %s", msg.Principal)
} }
return nil return nil
@ -255,11 +246,11 @@ func (msg MsgDrawDebt) String() string {
type MsgRepayDebt struct { type MsgRepayDebt struct {
Sender sdk.AccAddress `json:"sender" yaml:"sender"` Sender sdk.AccAddress `json:"sender" yaml:"sender"`
CdpDenom string `json:"cdp_denom" yaml:"cdp_denom"` CdpDenom string `json:"cdp_denom" yaml:"cdp_denom"`
Payment sdk.Coins `json:"payment" yaml:"payment"` Payment sdk.Coin `json:"payment" yaml:"payment"`
} }
// NewMsgRepayDebt returns a new MsgRepayDebt // NewMsgRepayDebt returns a new MsgRepayDebt
func NewMsgRepayDebt(sender sdk.AccAddress, denom string, payment sdk.Coins) MsgRepayDebt { func NewMsgRepayDebt(sender sdk.AccAddress, denom string, payment sdk.Coin) MsgRepayDebt {
return MsgRepayDebt{ return MsgRepayDebt{
Sender: sender, Sender: sender,
CdpDenom: denom, CdpDenom: denom,
@ -281,7 +272,7 @@ func (msg MsgRepayDebt) ValidateBasic() error {
if strings.TrimSpace(msg.CdpDenom) == "" { if strings.TrimSpace(msg.CdpDenom) == "" {
return errors.New("cdp denom cannot be blank") return errors.New("cdp denom cannot be blank")
} }
if msg.Payment.Empty() || !msg.Payment.IsValid() { if msg.Payment.IsZero() || !msg.Payment.IsValid() {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "payment amount %s", msg.Payment) return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "payment amount %s", msg.Payment)
} }
return nil return nil

View File

@ -8,9 +8,8 @@ import (
) )
var ( var (
coinsSingle = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000)) coinsSingle = sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000)
coinsZero = sdk.NewCoins() coinsZero = sdk.NewCoin(sdk.DefaultBondDenom, sdk.ZeroInt())
coinsMulti = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000), sdk.NewInt64Coin("foo", 10000)).Sort()
addrs = []sdk.AccAddress{ addrs = []sdk.AccAddress{
sdk.AccAddress("test1"), sdk.AccAddress("test1"),
sdk.AccAddress("test2"), sdk.AccAddress("test2"),
@ -21,15 +20,13 @@ func TestMsgCreateCDP(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
sender sdk.AccAddress sender sdk.AccAddress
collateral sdk.Coins collateral sdk.Coin
principal sdk.Coins principal sdk.Coin
expectPass bool expectPass bool
}{ }{
{"create cdp", addrs[0], coinsSingle, coinsSingle, true}, {"create cdp", addrs[0], coinsSingle, coinsSingle, true},
{"create cdp multi debt", addrs[0], coinsSingle, coinsMulti, true},
{"create cdp no collateral", addrs[0], coinsZero, coinsSingle, false}, {"create cdp no collateral", addrs[0], coinsZero, coinsSingle, false},
{"create cdp no debt", addrs[0], coinsSingle, coinsZero, false}, {"create cdp no debt", addrs[0], coinsSingle, coinsZero, false},
{"create cdp multi collateral", addrs[0], coinsMulti, coinsSingle, false},
{"create cdp empty owner", sdk.AccAddress{}, coinsSingle, coinsSingle, false}, {"create cdp empty owner", sdk.AccAddress{}, coinsSingle, coinsSingle, false},
} }
@ -52,13 +49,12 @@ func TestMsgDeposit(t *testing.T) {
description string description string
sender sdk.AccAddress sender sdk.AccAddress
depositor sdk.AccAddress depositor sdk.AccAddress
collateral sdk.Coins collateral sdk.Coin
expectPass bool expectPass bool
}{ }{
{"deposit", addrs[0], addrs[1], coinsSingle, true}, {"deposit", addrs[0], addrs[1], coinsSingle, true},
{"deposit", addrs[0], addrs[0], coinsSingle, true}, {"deposit", addrs[0], addrs[0], coinsSingle, true},
{"deposit no collateral", addrs[0], addrs[1], coinsZero, false}, {"deposit no collateral", addrs[0], addrs[1], coinsZero, false},
{"deposit multi collateral", addrs[0], addrs[1], coinsMulti, false},
{"deposit empty owner", sdk.AccAddress{}, addrs[1], coinsSingle, false}, {"deposit empty owner", sdk.AccAddress{}, addrs[1], coinsSingle, false},
{"deposit empty depositor", addrs[0], sdk.AccAddress{}, coinsSingle, false}, {"deposit empty depositor", addrs[0], sdk.AccAddress{}, coinsSingle, false},
} }
@ -82,13 +78,12 @@ func TestMsgWithdraw(t *testing.T) {
description string description string
sender sdk.AccAddress sender sdk.AccAddress
depositor sdk.AccAddress depositor sdk.AccAddress
collateral sdk.Coins collateral sdk.Coin
expectPass bool expectPass bool
}{ }{
{"withdraw", addrs[0], addrs[1], coinsSingle, true}, {"withdraw", addrs[0], addrs[1], coinsSingle, true},
{"withdraw", addrs[0], addrs[0], coinsSingle, true}, {"withdraw", addrs[0], addrs[0], coinsSingle, true},
{"withdraw no collateral", addrs[0], addrs[1], coinsZero, false}, {"withdraw no collateral", addrs[0], addrs[1], coinsZero, false},
{"withdraw multi collateral", addrs[0], addrs[1], coinsMulti, false},
{"withdraw empty owner", sdk.AccAddress{}, addrs[1], coinsSingle, false}, {"withdraw empty owner", sdk.AccAddress{}, addrs[1], coinsSingle, false},
{"withdraw empty depositor", addrs[0], sdk.AccAddress{}, coinsSingle, false}, {"withdraw empty depositor", addrs[0], sdk.AccAddress{}, coinsSingle, false},
} }
@ -112,12 +107,11 @@ func TestMsgDrawDebt(t *testing.T) {
description string description string
sender sdk.AccAddress sender sdk.AccAddress
denom string denom string
principal sdk.Coins principal sdk.Coin
expectPass bool expectPass bool
}{ }{
{"draw debt", addrs[0], sdk.DefaultBondDenom, coinsSingle, true}, {"draw debt", addrs[0], sdk.DefaultBondDenom, coinsSingle, true},
{"draw debt no debt", addrs[0], sdk.DefaultBondDenom, coinsZero, false}, {"draw debt no debt", addrs[0], sdk.DefaultBondDenom, coinsZero, false},
{"draw debt multi debt", addrs[0], sdk.DefaultBondDenom, coinsMulti, true},
{"draw debt empty owner", sdk.AccAddress{}, sdk.DefaultBondDenom, coinsSingle, false}, {"draw debt empty owner", sdk.AccAddress{}, sdk.DefaultBondDenom, coinsSingle, false},
{"draw debt empty denom", sdk.AccAddress{}, "", coinsSingle, false}, {"draw debt empty denom", sdk.AccAddress{}, "", coinsSingle, false},
} }
@ -141,12 +135,11 @@ func TestMsgRepayDebt(t *testing.T) {
description string description string
sender sdk.AccAddress sender sdk.AccAddress
denom string denom string
payment sdk.Coins payment sdk.Coin
expectPass bool expectPass bool
}{ }{
{"repay debt", addrs[0], sdk.DefaultBondDenom, coinsSingle, true}, {"repay debt", addrs[0], sdk.DefaultBondDenom, coinsSingle, true},
{"repay debt no payment", addrs[0], sdk.DefaultBondDenom, coinsZero, false}, {"repay debt no payment", addrs[0], sdk.DefaultBondDenom, coinsZero, false},
{"repay debt multi payment", addrs[0], sdk.DefaultBondDenom, coinsMulti, true},
{"repay debt empty owner", sdk.AccAddress{}, sdk.DefaultBondDenom, coinsSingle, false}, {"repay debt empty owner", sdk.AccAddress{}, sdk.DefaultBondDenom, coinsSingle, false},
{"repay debt empty denom", sdk.AccAddress{}, "", coinsSingle, false}, {"repay debt empty denom", sdk.AccAddress{}, "", coinsSingle, false},
} }

View File

@ -13,33 +13,41 @@ import (
// Parameter keys // Parameter keys
var ( var (
KeyGlobalDebtLimit = []byte("GlobalDebtLimit") KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
KeyCollateralParams = []byte("CollateralParams") KeyCollateralParams = []byte("CollateralParams")
KeyDebtParams = []byte("DebtParams") KeyDebtParam = []byte("DebtParam")
KeyDistributionFrequency = []byte("DistributionFrequency") KeyDistributionFrequency = []byte("DistributionFrequency")
KeyCircuitBreaker = []byte("CircuitBreaker") KeyCircuitBreaker = []byte("CircuitBreaker")
KeyDebtThreshold = []byte("DebtThreshold") KeyDebtThreshold = []byte("DebtThreshold")
KeySurplusThreshold = []byte("SurplusThreshold") KeySurplusThreshold = []byte("SurplusThreshold")
DefaultGlobalDebt = sdk.Coins{} DefaultGlobalDebt = sdk.NewCoin(DefaultStableDenom, sdk.ZeroInt())
DefaultCircuitBreaker = false DefaultCircuitBreaker = false
DefaultCollateralParams = CollateralParams{} DefaultCollateralParams = CollateralParams{}
DefaultDebtParams = DebtParams{} DefaultDebtParam = DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
}
DefaultCdpStartingID = uint64(1) DefaultCdpStartingID = uint64(1)
DefaultDebtDenom = "debt" DefaultDebtDenom = "debt"
DefaultGovDenom = "ukava" DefaultGovDenom = "ukava"
DefaultStableDenom = "usdx"
DefaultSurplusThreshold = sdk.NewInt(1000000000) DefaultSurplusThreshold = sdk.NewInt(1000000000)
DefaultDebtThreshold = sdk.NewInt(1000000000) DefaultDebtThreshold = sdk.NewInt(1000000000)
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0)) DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
DefaultSavingsDistributionFrequency = time.Hour * 24 * 2 DefaultSavingsDistributionFrequency = time.Hour * 12
minCollateralPrefix = 0 minCollateralPrefix = 0
maxCollateralPrefix = 255 maxCollateralPrefix = 255
stabilityFeeMax = sdk.MustNewDecFromStr("1.000000051034942716") // 500% APR
) )
// Params governance parameters for cdp module // Params governance parameters for cdp module
type Params struct { 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"` DebtParam DebtParam `json:"debt_param" yaml:"debt_param"`
GlobalDebtLimit sdk.Coins `json:"global_debt_limit" yaml:"global_debt_limit"` GlobalDebtLimit sdk.Coin `json:"global_debt_limit" yaml:"global_debt_limit"`
SurplusAuctionThreshold sdk.Int `json:"surplus_auction_threshold" yaml:"surplus_auction_threshold"` SurplusAuctionThreshold sdk.Int `json:"surplus_auction_threshold" yaml:"surplus_auction_threshold"`
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"` DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
SavingsDistributionFrequency time.Duration `json:"savings_distribution_frequency" yaml:"savings_distribution_frequency"` SavingsDistributionFrequency time.Duration `json:"savings_distribution_frequency" yaml:"savings_distribution_frequency"`
@ -56,16 +64,16 @@ func (p Params) String() string {
Debt Auction Threshold: %s Debt Auction Threshold: %s
Savings Distribution Frequency: %s Savings Distribution Frequency: %s
Circuit Breaker: %t`, Circuit Breaker: %t`,
p.GlobalDebtLimit, p.CollateralParams, p.DebtParams, p.SurplusAuctionThreshold, p.DebtAuctionThreshold, p.SavingsDistributionFrequency, p.CircuitBreaker, p.GlobalDebtLimit, p.CollateralParams, p.DebtParam, p.SurplusAuctionThreshold, p.DebtAuctionThreshold, p.SavingsDistributionFrequency, p.CircuitBreaker,
) )
} }
// NewParams returns a new params object // NewParams returns a new params object
func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParams DebtParams, surplusThreshold sdk.Int, debtThreshold sdk.Int, distributionFreq time.Duration, breaker bool) Params { func NewParams(debtLimit sdk.Coin, collateralParams CollateralParams, debtParam DebtParam, surplusThreshold sdk.Int, debtThreshold sdk.Int, distributionFreq time.Duration, breaker bool) Params {
return Params{ return Params{
GlobalDebtLimit: debtLimit, GlobalDebtLimit: debtLimit,
CollateralParams: collateralParams, CollateralParams: collateralParams,
DebtParams: debtParams, DebtParam: debtParam,
DebtAuctionThreshold: debtThreshold, DebtAuctionThreshold: debtThreshold,
SurplusAuctionThreshold: surplusThreshold, SurplusAuctionThreshold: surplusThreshold,
SavingsDistributionFrequency: distributionFreq, SavingsDistributionFrequency: distributionFreq,
@ -75,20 +83,20 @@ func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParam
// 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, DefaultSurplusThreshold, DefaultDebtThreshold, DefaultSavingsDistributionFrequency, DefaultCircuitBreaker) return NewParams(DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParam, DefaultSurplusThreshold, DefaultDebtThreshold, DefaultSavingsDistributionFrequency, DefaultCircuitBreaker)
} }
// CollateralParam governance parameters for each collateral type within the cdp module // CollateralParam governance parameters for each collateral type within the cdp module
type CollateralParam struct { type CollateralParam struct {
Denom string `json:"denom" yaml:"denom"` // Coin name of collateral type 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 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.Coin `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 `json:"auction_size" yaml:"auction_size"` // Max amount of collateral to sell off in any one auction. AuctionSize sdk.Int `json:"auction_size" yaml:"auction_size"` // Max amount of collateral to sell off in any one auction.
LiquidationPenalty sdk.Dec `json:"liquidation_penalty" yaml:"liquidation_penalty"` // percentage penalty (between [0, 1]) applied to a cdp if it is liquidated LiquidationPenalty sdk.Dec `json:"liquidation_penalty" yaml:"liquidation_penalty"` // 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
} }
// String implements fmt.Stringer // String implements fmt.Stringer
@ -132,7 +140,9 @@ func (dp DebtParam) String() string {
Denom: %s Denom: %s
Reference Asset: %s Reference Asset: %s
Conversion Factor: %s Conversion Factor: %s
Debt Floor %s`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor) Debt Floor %s
Savings Rate %s
`, dp.Denom, dp.ReferenceAsset, dp.ConversionFactor, dp.DebtFloor, dp.SavingsRate)
} }
// DebtParams array of DebtParam // DebtParams array of DebtParam
@ -159,7 +169,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
return params.ParamSetPairs{ return params.ParamSetPairs{
params.NewParamSetPair(KeyGlobalDebtLimit, &p.GlobalDebtLimit, validateGlobalDebtLimitParam), params.NewParamSetPair(KeyGlobalDebtLimit, &p.GlobalDebtLimit, validateGlobalDebtLimitParam),
params.NewParamSetPair(KeyCollateralParams, &p.CollateralParams, validateCollateralParams), params.NewParamSetPair(KeyCollateralParams, &p.CollateralParams, validateCollateralParams),
params.NewParamSetPair(KeyDebtParams, &p.DebtParams, validateDebtParams), params.NewParamSetPair(KeyDebtParam, &p.DebtParam, validateDebtParam),
params.NewParamSetPair(KeyCircuitBreaker, &p.CircuitBreaker, validateCircuitBreakerParam), params.NewParamSetPair(KeyCircuitBreaker, &p.CircuitBreaker, validateCircuitBreakerParam),
params.NewParamSetPair(KeySurplusThreshold, &p.SurplusAuctionThreshold, validateSurplusAuctionThresholdParam), params.NewParamSetPair(KeySurplusThreshold, &p.SurplusAuctionThreshold, validateSurplusAuctionThresholdParam),
params.NewParamSetPair(KeyDebtThreshold, &p.DebtAuctionThreshold, validateDebtAuctionThresholdParam), params.NewParamSetPair(KeyDebtThreshold, &p.DebtAuctionThreshold, validateDebtAuctionThresholdParam),
@ -177,7 +187,7 @@ func (p Params) Validate() error {
return err return err
} }
if err := validateDebtParams(p.DebtParams); err != nil { if err := validateDebtParam(p.DebtParam); err != nil {
return err return err
} }
@ -197,39 +207,50 @@ func (p Params) Validate() error {
return err return err
} }
debtDenoms := make(map[string]bool) if len(p.CollateralParams) == 0 { // default value OK
for _, dp := range p.DebtParams { return nil
debtDenoms[dp.Denom] = true }
if (DebtParam{}) != p.DebtParam {
if p.DebtParam.Denom != p.GlobalDebtLimit.Denom {
return fmt.Errorf("debt denom %s does not match global debt denom %s",
p.DebtParam.Denom, p.GlobalDebtLimit.Denom)
}
} }
// validate collateral params // validate collateral params
collateralDupMap := make(map[string]int) collateralDupMap := make(map[string]int)
prefixDupMap := make(map[int]int) prefixDupMap := make(map[int]int)
collateralParamsDebtLimit := sdk.Coins{} collateralParamsDebtLimit := sdk.ZeroInt()
for _, cp := range p.CollateralParams { for _, cp := range p.CollateralParams {
prefix := int(cp.Prefix) prefix := int(cp.Prefix)
prefixDupMap[prefix] = 1 prefixDupMap[prefix] = 1
collateralDupMap[cp.Denom] = 1 collateralDupMap[cp.Denom] = 1
collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit...) if cp.DebtLimit.Denom != p.GlobalDebtLimit.Denom {
return fmt.Errorf("collateral debt limit denom %s does not match global debt limit denom %s",
cp.DebtLimit.Denom, p.GlobalDebtLimit.Denom)
}
if cp.DebtLimit.IsAnyGT(p.GlobalDebtLimit) { collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit.Amount)
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) if cp.DebtLimit.Amount.GT(p.GlobalDebtLimit.Amount) {
return fmt.Errorf("collateral debt limit %s exceeds global debt limit: %s", cp.DebtLimit, p.GlobalDebtLimit)
} }
} }
if collateralParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { if collateralParamsDebtLimit.GT(p.GlobalDebtLimit.Amount) {
return fmt.Errorf("collateral debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tcollateral debt limits: %s", return fmt.Errorf("sum of collateral debt limits %s exceeds global debt limit %s",
p.GlobalDebtLimit, collateralParamsDebtLimit) collateralParamsDebtLimit, p.GlobalDebtLimit)
} }
return nil return nil
} }
func validateGlobalDebtLimitParam(i interface{}) error { func validateGlobalDebtLimitParam(i interface{}) error {
globalDebtLimit, ok := i.(sdk.Coins) globalDebtLimit, ok := i.(sdk.Coin)
if !ok { if !ok {
return fmt.Errorf("invalid parameter type: %T", i) return fmt.Errorf("invalid parameter type: %T", i)
} }
@ -250,8 +271,12 @@ func validateCollateralParams(i interface{}) error {
collateralDupMap := make(map[string]bool) collateralDupMap := make(map[string]bool)
prefixDupMap := make(map[int]bool) prefixDupMap := make(map[int]bool)
for _, cp := range collateralParams { for _, cp := range collateralParams {
if strings.TrimSpace(cp.Denom) == "" { if err := sdk.ValidateDenom(cp.Denom); err != nil {
return fmt.Errorf("debt denom cannot be blank %s", cp) return fmt.Errorf("collateral denom invalid %s", cp.Denom)
}
if strings.TrimSpace(cp.MarketID) == "" {
return fmt.Errorf("market id cannot be blank %s", cp)
} }
prefix := int(cp.Prefix) prefix := int(cp.Prefix)
@ -283,39 +308,26 @@ func validateCollateralParams(i interface{}) error {
if !cp.AuctionSize.IsPositive() { if !cp.AuctionSize.IsPositive() {
return fmt.Errorf("auction size should be positive, is %s for %s", cp.AuctionSize, cp.Denom) return fmt.Errorf("auction size should be positive, is %s for %s", cp.AuctionSize, cp.Denom)
} }
if cp.StabilityFee.LT(sdk.OneDec()) { if cp.StabilityFee.LT(sdk.OneDec()) || cp.StabilityFee.GT(stabilityFeeMax) {
return fmt.Errorf("stability fee must be ≥ 1.0, is %s for %s", cp.StabilityFee, cp.Denom) return fmt.Errorf("stability fee must be ≥ 1.0, ≤ %s, is %s for %s", stabilityFeeMax, cp.StabilityFee, cp.Denom)
} }
} }
return nil return nil
} }
func validateDebtParams(i interface{}) error { func validateDebtParam(i interface{}) error {
debtParams, ok := i.(DebtParams) debtParam, ok := i.(DebtParam)
if !ok { if !ok {
return fmt.Errorf("invalid parameter type: %T", i) return fmt.Errorf("invalid parameter type: %T", i)
} }
if err := sdk.ValidateDenom(debtParam.Denom); err != nil {
// validate debt params return fmt.Errorf("debt denom invalid %s", debtParam.Denom)
debtDenoms := make(map[string]bool)
for _, dp := range debtParams {
if strings.TrimSpace(dp.Denom) == "" {
return fmt.Errorf("debt denom cannot be blank %s", dp)
}
_, found := debtDenoms[dp.Denom]
if found {
return fmt.Errorf("duplicate debt denom: %s", dp.Denom)
}
if dp.SavingsRate.LT(sdk.ZeroDec()) || dp.SavingsRate.GT(sdk.OneDec()) {
return fmt.Errorf("savings rate should be between 0 and 1, is %s for %s", dp.SavingsRate, dp.Denom)
}
debtDenoms[dp.Denom] = true
} }
if debtParam.SavingsRate.LT(sdk.ZeroDec()) || debtParam.SavingsRate.GT(sdk.OneDec()) {
return fmt.Errorf("savings rate should be between 0 and 1, is %s for %s", debtParam.SavingsRate, debtParam.Denom)
}
return nil return nil
} }

736
x/cdp/types/params_test.go Normal file
View File

@ -0,0 +1,736 @@
package types_test
import (
"strings"
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/cdp/types"
"github.com/stretchr/testify/suite"
)
type ParamsTestSuite struct {
suite.Suite
}
func (suite *ParamsTestSuite) SetupTest() {
}
func (suite *ParamsTestSuite) TestParamValidation() {
type args struct {
globalDebtLimit sdk.Coin
collateralParams types.CollateralParams
debtParam types.DebtParam
surplusThreshold sdk.Int
debtThreshold sdk.Int
distributionFreq time.Duration
breaker bool
}
type errArgs struct {
expectPass bool
contains string
}
testCases := []struct {
name string
args args
errArgs errArgs
}{
{
name: "default",
args: args{
globalDebtLimit: types.DefaultGlobalDebt,
collateralParams: types.DefaultCollateralParams,
debtParam: types.DefaultDebtParam,
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "valid single-collateral",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 4000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "invalid single-collateral mismatched debt denoms",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 4000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "does not match global debt denom",
},
},
{
name: "invalid single-collateral over debt limit",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "exceeds global debt limit",
},
},
{
name: "valid multi-collateral",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 4000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
{
Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x21,
MarketID: "xrp:usd",
ConversionFactor: sdk.NewInt(6),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: true,
contains: "",
},
},
{
name: "invalid multi-collateral over debt limit",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
{
Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x21,
MarketID: "xrp:usd",
ConversionFactor: sdk.NewInt(6),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "sum of collateral debt limits",
},
},
{
name: "invalid multi-collateral multiple debt denoms",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 4000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
{
Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("susd", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x21,
MarketID: "xrp:usd",
ConversionFactor: sdk.NewInt(6),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "does not match global debt limit denom",
},
},
{
name: "invalid collateral params empty denom",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "collateral denom invalid",
},
},
{
name: "invalid collateral params empty market id",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "market id cannot be blank",
},
},
{
name: "invalid collateral params duplicate denom",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x21,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "duplicate collateral denom",
},
},
{
name: "invalid collateral params duplicate prefix",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
{
Denom: "xrp",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "xrp:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "duplicate prefix for collateral denom",
},
},
{
name: "invalid collateral params nil debt limit",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.Coin{},
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "debt limit for all collaterals should be positive",
},
},
{
name: "invalid collateral params liquidation ratio out of range",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("1.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "liquidation penalty should be between 0 and 1",
},
},
{
name: "invalid collateral params auction size zero",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.ZeroInt(),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "auction size should be positive",
},
},
{
name: "invalid collateral params stability fee out of range",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.1"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "stability fee must be ≥ 1.0",
},
},
{
name: "invalid debt param empty denom",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("0.95"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "debt denom invalid",
},
},
{
name: "invalid debt param savings rate out of range",
args: args{
globalDebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
collateralParams: types.CollateralParams{
{
Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("1.5"),
DebtLimit: sdk.NewInt64Coin("usdx", 2000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"),
LiquidationPenalty: sdk.MustNewDecFromStr("0.05"),
AuctionSize: sdk.NewInt(50000000000),
Prefix: 0x20,
MarketID: "bnb:usd",
ConversionFactor: sdk.NewInt(8),
},
},
debtParam: types.DebtParam{
Denom: "usdx",
ReferenceAsset: "usd",
ConversionFactor: sdk.NewInt(6),
DebtFloor: sdk.NewInt(10000000),
SavingsRate: sdk.MustNewDecFromStr("1.05"),
},
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "savings rate should be between 0 and 1",
},
},
{
name: "nil debt limit",
args: args{
globalDebtLimit: sdk.Coin{},
collateralParams: types.DefaultCollateralParams,
debtParam: types.DefaultDebtParam,
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "invalid coins: global debt limit",
},
},
{
name: "zero savings distribution frequency",
args: args{
globalDebtLimit: types.DefaultGlobalDebt,
collateralParams: types.DefaultCollateralParams,
debtParam: types.DefaultDebtParam,
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: time.Second * 0,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "savings distribution frequency should be positive",
},
},
{
name: "zero surplus auction",
args: args{
globalDebtLimit: types.DefaultGlobalDebt,
collateralParams: types.DefaultCollateralParams,
debtParam: types.DefaultDebtParam,
surplusThreshold: sdk.ZeroInt(),
debtThreshold: types.DefaultDebtThreshold,
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "surplus auction threshold should be positive",
},
},
{
name: "zero debt auction",
args: args{
globalDebtLimit: types.DefaultGlobalDebt,
collateralParams: types.DefaultCollateralParams,
debtParam: types.DefaultDebtParam,
surplusThreshold: types.DefaultSurplusThreshold,
debtThreshold: sdk.ZeroInt(),
distributionFreq: types.DefaultSavingsDistributionFrequency,
breaker: types.DefaultCircuitBreaker,
},
errArgs: errArgs{
expectPass: false,
contains: "debt auction threshold should be positive",
},
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
params := types.NewParams(tc.args.globalDebtLimit, tc.args.collateralParams, tc.args.debtParam, tc.args.surplusThreshold, tc.args.debtThreshold, tc.args.distributionFreq, tc.args.breaker)
err := params.Validate()
if tc.errArgs.expectPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
suite.Require().True(strings.Contains(err.Error(), tc.errArgs.contains))
}
})
}
}
func TestParamsTestSuite(t *testing.T) {
suite.Run(t, new(ParamsTestSuite))
}

View File

@ -72,7 +72,7 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed) rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed)
id := k.GetNextClaimPeriodID(ctx, rp.Denom) id := k.GetNextClaimPeriodID(ctx, rp.Denom)
k.cdpKeeper.IterateCdpsByDenom(ctx, rp.Denom, func(cdp cdptypes.CDP) bool { k.cdpKeeper.IterateCdpsByDenom(ctx, rp.Denom, func(cdp cdptypes.CDP) bool {
rewardsShare := sdk.NewDecFromInt(cdp.Principal.AmountOf(types.PrincipalDenom).Add(cdp.AccumulatedFees.AmountOf(types.PrincipalDenom))).Quo(sdk.NewDecFromInt(totalPrincipal)) rewardsShare := sdk.NewDecFromInt(cdp.Principal.Amount.Add(cdp.AccumulatedFees.Amount)).Quo(sdk.NewDecFromInt(totalPrincipal))
// sanity check - don't create zero claims // sanity check - don't create zero claims
if rewardsShare.IsZero() { if rewardsShare.IsZero() {
return false return false

View File

@ -175,7 +175,7 @@ func (suite *KeeperTestSuite) setupCdpChain() {
// need incentive params for one collateral // need incentive params for one collateral
cdpGS := cdp.GenesisState{ cdpGS := cdp.GenesisState{
Params: cdp.Params{ Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), GlobalDebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold, DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
@ -183,7 +183,7 @@ func (suite *KeeperTestSuite) setupCdpChain() {
{ {
Denom: "bnb", Denom: "bnb",
LiquidationRatio: sdk.MustNewDecFromStr("2.0"), LiquidationRatio: sdk.MustNewDecFromStr("2.0"),
DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), DebtLimit: sdk.NewInt64Coin("usdx", 1000000000000),
StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr
LiquidationPenalty: d("0.05"), LiquidationPenalty: d("0.05"),
AuctionSize: i(10000000000), AuctionSize: i(10000000000),
@ -192,14 +192,12 @@ func (suite *KeeperTestSuite) setupCdpChain() {
ConversionFactor: i(8), ConversionFactor: i(8),
}, },
}, },
DebtParams: cdp.DebtParams{ DebtParam: cdp.DebtParam{
{ Denom: "usdx",
Denom: "usdx", ReferenceAsset: "usd",
ReferenceAsset: "usd", ConversionFactor: i(6),
ConversionFactor: i(6), DebtFloor: i(10000000),
DebtFloor: i(10000000), SavingsRate: d("0.95"),
SavingsRate: d("0.95"),
},
}, },
}, },
StartingCdpID: cdp.DefaultCdpStartingID, StartingCdpID: cdp.DefaultCdpStartingID,
@ -239,11 +237,11 @@ func (suite *KeeperTestSuite) setupCdpChain() {
suite.ctx = ctx suite.ctx = ctx
// create 3 cdps // create 3 cdps
cdpKeeper := tApp.GetCDPKeeper() cdpKeeper := tApp.GetCDPKeeper()
err := cdpKeeper.AddCdp(suite.ctx, addrs[0], cs(c("bnb", 10000000000)), cs(c("usdx", 10000000))) err := cdpKeeper.AddCdp(suite.ctx, addrs[0], c("bnb", 10000000000), c("usdx", 10000000))
suite.Require().NoError(err) suite.Require().NoError(err)
err = cdpKeeper.AddCdp(suite.ctx, addrs[1], cs(c("bnb", 100000000000)), cs(c("usdx", 100000000))) err = cdpKeeper.AddCdp(suite.ctx, addrs[1], c("bnb", 100000000000), c("usdx", 100000000))
suite.Require().NoError(err) suite.Require().NoError(err)
err = cdpKeeper.AddCdp(suite.ctx, addrs[2], cs(c("bnb", 1000000000000)), cs(c("usdx", 1000000000))) err = cdpKeeper.AddCdp(suite.ctx, addrs[2], c("bnb", 1000000000000), c("usdx", 1000000000))
suite.Require().NoError(err) suite.Require().NoError(err)
// total usd is 1110 // total usd is 1110