2019-11-25 19:46:02 +00:00
package keeper
import (
"fmt"
"testing"
2019-11-28 16:53:59 +00:00
"time"
2019-11-25 19:46:02 +00:00
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/pricefeed"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
2019-11-28 16:53:59 +00:00
tmtime "github.com/tendermint/tendermint/types/time"
2019-11-25 19:46:02 +00:00
)
// How could one reduce the number of params in the test cases. Create a table driven test for each of the 4 add/withdraw collateral/debt?
// These are more like app level tests - I think this is a symptom of having 'ModifyCDP' do a lot. Could be easier for testing purposes to break it down.
func TestKeeper_ModifyCDP ( t * testing . T ) {
_ , addrs := mock . GeneratePrivKeyAddressPairs ( 1 )
ownerAddr := addrs [ 0 ]
type state struct { // TODO this allows invalid state to be set up, should it?
CDP types . CDP
OwnerCoins sdk . Coins
GlobalDebt sdk . Int
CollateralState types . CollateralState
}
type args struct {
owner sdk . AccAddress
collateralDenom string
changeInCollateral sdk . Int
changeInDebt sdk . Int
}
tests := [ ] struct {
name string
priorState state
price string
// also missing CDPModuleParams
args args
expectPass bool
expectedState state
} {
{
"addCollateralAndDecreaseDebt" ,
state { types . CDP { ownerAddr , "xrp" , i ( 100 ) , i ( 2 ) } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 2 ) ) , i ( 2 ) , types . CollateralState { "xrp" , i ( 2 ) } } ,
"10.345" ,
args { ownerAddr , "xrp" , i ( 10 ) , i ( - 1 ) } ,
true ,
state { types . CDP { ownerAddr , "xrp" , i ( 110 ) , i ( 1 ) } , cs ( /* 0xrp */ c ( "usdx" , 1 ) ) , i ( 1 ) , types . CollateralState { "xrp" , i ( 1 ) } } ,
} ,
{
"removeTooMuchCollateral" ,
state { types . CDP { ownerAddr , "xrp" , i ( 1000 ) , i ( 200 ) } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 10 ) ) , i ( 200 ) , types . CollateralState { "xrp" , i ( 200 ) } } ,
"1.00" ,
args { ownerAddr , "xrp" , i ( - 601 ) , i ( 0 ) } ,
false ,
state { types . CDP { ownerAddr , "xrp" , i ( 1000 ) , i ( 200 ) } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 10 ) ) , i ( 200 ) , types . CollateralState { "xrp" , i ( 200 ) } } ,
} ,
{
"withdrawTooMuchStableCoin" ,
state { types . CDP { ownerAddr , "xrp" , i ( 1000 ) , i ( 200 ) } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 10 ) ) , i ( 200 ) , types . CollateralState { "xrp" , i ( 200 ) } } ,
"1.00" ,
args { ownerAddr , "xrp" , i ( 0 ) , i ( 301 ) } ,
false ,
state { types . CDP { ownerAddr , "xrp" , i ( 1000 ) , i ( 200 ) } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 10 ) ) , i ( 200 ) , types . CollateralState { "xrp" , i ( 200 ) } } ,
} ,
{
"createCDPAndWithdrawStable" ,
state { types . CDP { } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 10 ) ) , i ( 0 ) , types . CollateralState { "xrp" , i ( 0 ) } } ,
"1.00" ,
args { ownerAddr , "xrp" , i ( 5 ) , i ( 2 ) } ,
true ,
state { types . CDP { ownerAddr , "xrp" , i ( 5 ) , i ( 2 ) } , cs ( c ( "xrp" , 5 ) , c ( "usdx" , 12 ) ) , i ( 2 ) , types . CollateralState { "xrp" , i ( 2 ) } } ,
} ,
{
"emptyCDP" ,
state { types . CDP { ownerAddr , "xrp" , i ( 1000 ) , i ( 200 ) } , cs ( c ( "xrp" , 10 ) , c ( "usdx" , 201 ) ) , i ( 200 ) , types . CollateralState { "xrp" , i ( 200 ) } } ,
"1.00" ,
args { ownerAddr , "xrp" , i ( - 1000 ) , i ( - 200 ) } ,
true ,
state { types . CDP { } , cs ( c ( "xrp" , 1010 ) , c ( "usdx" , 1 ) ) , i ( 0 ) , types . CollateralState { "xrp" , i ( 0 ) } } ,
} ,
{
"invalidCollateralType" ,
state { types . CDP { } , cs ( c ( "shitcoin" , 5000000 ) ) , i ( 0 ) , types . CollateralState { } } ,
"0.000001" ,
args { ownerAddr , "shitcoin" , i ( 5000000 ) , i ( 1 ) } , // ratio of 5:1
false ,
state { types . CDP { } , cs ( c ( "shitcoin" , 5000000 ) ) , i ( 0 ) , types . CollateralState { } } ,
} ,
}
for _ , tc := range tests {
t . Run ( tc . name , func ( t * testing . T ) {
// setup keeper
mapp , keeper , _ , _ := setUpMockAppWithoutGenesis ( )
// initialize cdp owner account with coins
genAcc := auth . BaseAccount {
Address : ownerAddr ,
Coins : tc . priorState . OwnerCoins ,
}
mock . SetGenesis ( mapp , [ ] authexported . Account { & genAcc } )
// create a new context
2019-11-28 16:53:59 +00:00
header := abci . Header { Height : mapp . LastBlockHeight ( ) + 1 , Time : tmtime . Now ( ) }
2019-11-25 19:46:02 +00:00
mapp . BeginBlock ( abci . RequestBeginBlock { Header : header } )
ctx := mapp . BaseApp . NewContext ( false , header )
keeper . SetParams ( ctx , defaultParamsSingle ( ) )
// setup store state
2019-11-28 16:53:59 +00:00
ap := pricefeed . Params {
2019-11-25 19:46:02 +00:00
Assets : [ ] pricefeed . Asset {
2019-11-28 16:53:59 +00:00
pricefeed . Asset {
AssetCode : "xrp" , BaseAsset : "xrp" ,
QuoteAsset : "usd" , Oracles : pricefeed . Oracles { } , Active : true } ,
2019-11-25 19:46:02 +00:00
} ,
}
2019-11-28 16:53:59 +00:00
keeper . pricefeedKeeper . SetParams ( ctx , ap )
2019-11-25 19:46:02 +00:00
_ , err := keeper . pricefeedKeeper . SetPrice (
ctx , ownerAddr , "xrp" ,
sdk . MustNewDecFromStr ( tc . price ) ,
2019-11-28 16:53:59 +00:00
header . Time . Add ( time . Hour * 1 ) )
2019-11-25 19:46:02 +00:00
if err != nil {
t . Log ( "test context height" , ctx . BlockHeight ( ) )
t . Log ( err )
t . Log ( tc . name )
}
2019-11-28 16:53:59 +00:00
err = keeper . pricefeedKeeper . SetCurrentPrices ( ctx , "xrp" )
2019-11-25 19:46:02 +00:00
if err != nil {
t . Log ( "test context height" , ctx . BlockHeight ( ) )
t . Log ( err )
t . Log ( tc . name )
}
if tc . priorState . CDP . CollateralDenom != "" { // check if the prior CDP should be created or not (see if an empty one was specified)
keeper . SetCDP ( ctx , tc . priorState . CDP )
}
keeper . SetGlobalDebt ( ctx , tc . priorState . GlobalDebt )
if tc . priorState . CollateralState . Denom != "" {
keeper . setCollateralState ( ctx , tc . priorState . CollateralState )
}
// call func under test
err = keeper . ModifyCDP ( ctx , tc . args . owner , tc . args . collateralDenom , tc . args . changeInCollateral , tc . args . changeInDebt )
mapp . EndBlock ( abci . RequestEndBlock { } )
mapp . Commit ( )
// get new state for verification
actualCDP , found := keeper . GetCDP ( ctx , tc . args . owner , tc . args . collateralDenom )
// check for err
if tc . expectPass {
require . NoError ( t , err , fmt . Sprint ( err ) )
} else {
require . Error ( t , err )
}
actualGDebt := keeper . GetGlobalDebt ( ctx )
actualCstate , _ := keeper . GetCollateralState ( ctx , tc . args . collateralDenom )
// check state
require . Equal ( t , tc . expectedState . CDP , actualCDP )
if tc . expectedState . CDP . CollateralDenom == "" { // if the expected CDP is blank, then expect the CDP to have been deleted (hence not found)
require . False ( t , found )
} else {
require . True ( t , found )
}
require . Equal ( t , tc . expectedState . GlobalDebt , actualGDebt )
require . Equal ( t , tc . expectedState . CollateralState , actualCstate )
// check owner balance
mock . CheckBalance ( t , mapp , ownerAddr , tc . expectedState . OwnerCoins )
} )
}
}
// TODO change to table driven test to test more test cases
func TestKeeper_PartialSeizeCDP ( t * testing . T ) {
// Setup
const collateral = "xrp"
mapp , keeper , _ , _ := setUpMockAppWithoutGenesis ( )
genAccs , addrs , _ , _ := mock . CreateGenAccounts ( 1 , cs ( c ( collateral , 100 ) ) )
testAddr := addrs [ 0 ]
mock . SetGenesis ( mapp , genAccs )
// setup pricefeed
2019-11-28 16:53:59 +00:00
header := abci . Header { Height : mapp . LastBlockHeight ( ) + 1 , Time : tmtime . Now ( ) }
2019-11-25 19:46:02 +00:00
mapp . BeginBlock ( abci . RequestBeginBlock { Header : header } )
ctx := mapp . BaseApp . NewContext ( false , header )
keeper . SetParams ( ctx , defaultParamsSingle ( ) )
2019-11-28 16:53:59 +00:00
ap := pricefeed . Params {
2019-11-25 19:46:02 +00:00
Assets : [ ] pricefeed . Asset {
2019-11-28 16:53:59 +00:00
pricefeed . Asset {
AssetCode : "xrp" , BaseAsset : "xrp" ,
QuoteAsset : "usd" , Oracles : pricefeed . Oracles { } , Active : true } ,
2019-11-25 19:46:02 +00:00
} ,
}
2019-11-28 16:53:59 +00:00
keeper . pricefeedKeeper . SetParams ( ctx , ap )
2019-11-25 19:46:02 +00:00
keeper . pricefeedKeeper . SetPrice (
ctx , sdk . AccAddress { } , collateral ,
sdk . MustNewDecFromStr ( "1.00" ) ,
2019-11-28 16:53:59 +00:00
header . Time . Add ( time . Hour * 1 ) )
keeper . pricefeedKeeper . SetCurrentPrices ( ctx , collateral )
2019-11-25 19:46:02 +00:00
// Create CDP
keeper . SetGlobalDebt ( ctx , i ( 0 ) )
err := keeper . ModifyCDP ( ctx , testAddr , collateral , i ( 10 ) , i ( 5 ) )
require . NoError ( t , err )
// Reduce price
keeper . pricefeedKeeper . SetPrice (
ctx , sdk . AccAddress { } , collateral ,
sdk . MustNewDecFromStr ( "0.90" ) ,
2019-11-28 16:53:59 +00:00
header . Time . Add ( time . Hour * 1 ) )
keeper . pricefeedKeeper . SetCurrentPrices ( ctx , collateral )
2019-11-25 19:46:02 +00:00
// Seize entire CDP
err = keeper . PartialSeizeCDP ( ctx , testAddr , collateral , i ( 10 ) , i ( 5 ) )
// Check
require . NoError ( t , err )
_ , found := keeper . GetCDP ( ctx , testAddr , collateral )
require . False ( t , found )
collateralState , found := keeper . GetCollateralState ( ctx , collateral )
require . True ( t , found )
require . Equal ( t , sdk . ZeroInt ( ) , collateralState . TotalDebt )
}
func TestKeeper_GetCDPs ( t * testing . T ) {
// setup keeper
mapp , keeper , _ , _ := setUpMockAppWithoutGenesis ( )
mock . SetGenesis ( mapp , [ ] authexported . Account ( nil ) )
header := abci . Header { Height : mapp . LastBlockHeight ( ) + 1 }
mapp . BeginBlock ( abci . RequestBeginBlock { Header : header } )
ctx := mapp . BaseApp . NewContext ( false , header )
keeper . SetParams ( ctx , defaultParamsMulti ( ) )
// setup CDPs
_ , addrs := mock . GeneratePrivKeyAddressPairs ( 2 )
cdps := types . CDPs {
{ addrs [ 0 ] , "xrp" , i ( 4000 ) , i ( 5 ) } ,
{ addrs [ 1 ] , "xrp" , i ( 4000 ) , i ( 2000 ) } ,
{ addrs [ 0 ] , "btc" , i ( 10 ) , i ( 20 ) } ,
}
for _ , cdp := range cdps {
keeper . SetCDP ( ctx , cdp )
}
// Check nil params returns all CDPs
returnedCdps , err := keeper . GetCDPs ( ctx , "" , sdk . Dec { } )
require . NoError ( t , err )
require . Equal ( t ,
types . CDPs {
{ addrs [ 0 ] , "btc" , i ( 10 ) , i ( 20 ) } ,
{ addrs [ 1 ] , "xrp" , i ( 4000 ) , i ( 2000 ) } ,
{ addrs [ 0 ] , "xrp" , i ( 4000 ) , i ( 5 ) } } ,
returnedCdps ,
)
// Check correct CDPs filtered by collateral and sorted
returnedCdps , err = keeper . GetCDPs ( ctx , "xrp" , d ( "0.00000001" ) )
require . NoError ( t , err )
require . Equal ( t ,
types . CDPs {
{ addrs [ 1 ] , "xrp" , i ( 4000 ) , i ( 2000 ) } ,
{ addrs [ 0 ] , "xrp" , i ( 4000 ) , i ( 5 ) } } ,
returnedCdps ,
)
returnedCdps , err = keeper . GetCDPs ( ctx , "xrp" , sdk . Dec { } )
require . NoError ( t , err )
require . Equal ( t ,
types . CDPs {
{ addrs [ 1 ] , "xrp" , i ( 4000 ) , i ( 2000 ) } ,
{ addrs [ 0 ] , "xrp" , i ( 4000 ) , i ( 5 ) } } ,
returnedCdps ,
)
returnedCdps , err = keeper . GetCDPs ( ctx , "xrp" , d ( "0.9" ) )
require . NoError ( t , err )
require . Equal ( t ,
types . CDPs {
{ addrs [ 1 ] , "xrp" , i ( 4000 ) , i ( 2000 ) } } ,
returnedCdps ,
)
// Check high price returns no CDPs
returnedCdps , err = keeper . GetCDPs ( ctx , "xrp" , d ( "999999999.99" ) )
require . NoError ( t , err )
require . Equal ( t ,
types . CDPs ( nil ) ,
returnedCdps ,
)
// Check unauthorized collateral denom returns error
_ , err = keeper . GetCDPs ( ctx , "a non existent coin" , d ( "0.34023" ) )
require . Error ( t , err )
// Check price without collateral returns error
_ , err = keeper . GetCDPs ( ctx , "" , d ( "0.34023" ) )
require . Error ( t , err )
// Check deleting a CDP removes it
keeper . deleteCDP ( ctx , cdps [ 0 ] )
returnedCdps , err = keeper . GetCDPs ( ctx , "" , sdk . Dec { } )
require . NoError ( t , err )
require . Equal ( t ,
types . CDPs {
{ addrs [ 0 ] , "btc" , i ( 10 ) , i ( 20 ) } ,
{ addrs [ 1 ] , "xrp" , i ( 4000 ) , i ( 2000 ) } } ,
returnedCdps ,
)
}
func TestKeeper_GetSetDeleteCDP ( t * testing . T ) {
// setup keeper, create CDP
mapp , keeper , _ , _ := setUpMockAppWithoutGenesis ( )
header := abci . Header { Height : mapp . LastBlockHeight ( ) + 1 }
mapp . BeginBlock ( abci . RequestBeginBlock { Header : header } )
ctx := mapp . BaseApp . NewContext ( false , header )
keeper . SetParams ( ctx , defaultParamsSingle ( ) )
_ , addrs := mock . GeneratePrivKeyAddressPairs ( 1 )
cdp := types . CDP { addrs [ 0 ] , "xrp" , i ( 412 ) , i ( 56 ) }
// write and read from store
keeper . SetCDP ( ctx , cdp )
readCDP , found := keeper . GetCDP ( ctx , cdp . Owner , cdp . CollateralDenom )
// check before and after match
require . True ( t , found )
require . Equal ( t , cdp , readCDP )
// delete auction
keeper . deleteCDP ( ctx , cdp )
// check auction does not exist
_ , found = keeper . GetCDP ( ctx , cdp . Owner , cdp . CollateralDenom )
require . False ( t , found )
}
func TestKeeper_GetSetGDebt ( t * testing . T ) {
// setup keeper, create GDebt
mapp , keeper , _ , _ := setUpMockAppWithoutGenesis ( )
header := abci . Header { Height : mapp . LastBlockHeight ( ) + 1 }
mapp . BeginBlock ( abci . RequestBeginBlock { Header : header } )
ctx := mapp . BaseApp . NewContext ( false , header )
keeper . SetParams ( ctx , defaultParamsSingle ( ) )
gDebt := i ( 4120000 )
// write and read from store
keeper . SetGlobalDebt ( ctx , gDebt )
readGDebt := keeper . GetGlobalDebt ( ctx )
// check before and after match
require . Equal ( t , gDebt , readGDebt )
}
func TestKeeper_GetSetCollateralState ( t * testing . T ) {
// setup keeper, create CState
mapp , keeper , _ , _ := setUpMockAppWithoutGenesis ( )
header := abci . Header { Height : mapp . LastBlockHeight ( ) + 1 }
mapp . BeginBlock ( abci . RequestBeginBlock { Header : header } )
ctx := mapp . BaseApp . NewContext ( false , header )
keeper . SetParams ( ctx , defaultParamsSingle ( ) )
collateralState := types . CollateralState { "xrp" , i ( 15400 ) }
// write and read from store
keeper . setCollateralState ( ctx , collateralState )
readCState , found := keeper . GetCollateralState ( ctx , collateralState . Denom )
// check before and after match
require . Equal ( t , collateralState , readCState )
require . True ( t , found )
}