diff --git a/app/app.go b/app/app.go index be60f667..088e26a5 100644 --- a/app/app.go +++ b/app/app.go @@ -4,6 +4,10 @@ import ( "io" "os" + "github.com/kava-labs/kava/x/auction" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/liquidator" + "github.com/kava-labs/kava/x/pricefeed" validatorvesting "github.com/kava-labs/kava/x/validator-vesting" abci "github.com/tendermint/tendermint/abci/types" @@ -55,6 +59,10 @@ var ( crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, supply.AppModuleBasic{}, + auction.AppModuleBasic{}, + cdp.AppModuleBasic{}, + liquidator.AppModuleBasic{}, + pricefeed.AppModuleBasic{}, ) // module account permissions @@ -81,17 +89,21 @@ type App struct { tkeys map[string]*sdk.TransientStoreKey // keepers from all the modules - accountKeeper auth.AccountKeeper - bankKeeper bank.Keeper - supplyKeeper supply.Keeper - stakingKeeper staking.Keeper - slashingKeeper slashing.Keeper - mintKeeper mint.Keeper - distrKeeper distr.Keeper - govKeeper gov.Keeper - crisisKeeper crisis.Keeper - paramsKeeper params.Keeper - vvKeeper validatorvesting.Keeper + accountKeeper auth.AccountKeeper + bankKeeper bank.Keeper + supplyKeeper supply.Keeper + stakingKeeper staking.Keeper + slashingKeeper slashing.Keeper + mintKeeper mint.Keeper + distrKeeper distr.Keeper + govKeeper gov.Keeper + crisisKeeper crisis.Keeper + paramsKeeper params.Keeper + vvKeeper validatorvesting.Keeper + auctionKeeper auction.Keeper + cdpKeeper cdp.Keeper + liquidatorKeeper liquidator.Keeper + pricefeedKeeper pricefeed.Keeper // the module manager mm *module.Manager @@ -115,6 +127,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, validatorvesting.StoreKey, + auction.StoreKey, cdp.StoreKey, liquidator.StoreKey, pricefeed.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) @@ -136,6 +149,10 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) + auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace) + cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace) + liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace) + pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace) // add keepers app.accountKeeper = auth.NewAccountKeeper( @@ -207,6 +224,29 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.bankKeeper, app.supplyKeeper, &stakingKeeper) + app.pricefeedKeeper = pricefeed.NewKeeper( + app.cdc, + keys[pricefeed.StoreKey], + pricefeedSubspace, + pricefeed.DefaultCodespace) + app.cdpKeeper = cdp.NewKeeper( + app.cdc, + keys[cdp.StoreKey], + cdpSubspace, + app.pricefeedKeeper, + app.bankKeeper) + app.auctionKeeper = auction.NewKeeper( + app.cdc, + app.cdpKeeper, // CDP keeper standing in for bank + keys[auction.StoreKey], + auctionSubspace) + app.liquidatorKeeper = liquidator.NewKeeper( + app.cdc, + keys[liquidator.StoreKey], + liquidatorSubspace, + app.cdpKeeper, + app.auctionKeeper, + app.cdpKeeper) // CDP keeper standing in for bank // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks @@ -227,6 +267,10 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper), + auction.NewAppModule(app.auctionKeeper), + cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper), + liquidator.NewAppModule(app.liquidatorKeeper), + pricefeed.NewAppModule(app.pricefeedKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -234,7 +278,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, // CanWithdrawInvariant invariant. app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName) - app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName) + app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName, auction.ModuleName) // TODO is this correct order? // Note: genutils must occur after staking so that pools are properly // initialized with tokens from genesis accounts. @@ -245,6 +289,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, auth.ModuleName, validatorvesting.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, + pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, liquidator.ModuleName, // TODO is this order ok? ) app.mm.RegisterInvariants(&app.crisisKeeper) diff --git a/app/test_common.go b/app/test_common.go new file mode 100644 index 00000000..f6a99036 --- /dev/null +++ b/app/test_common.go @@ -0,0 +1,136 @@ +package app + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/tendermint/tendermint/libs/log" + tmdb "github.com/tendermint/tm-db" + + "github.com/cosmos/cosmos-sdk/codec" + 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/bank" + "github.com/cosmos/cosmos-sdk/x/crisis" + "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/slashing" + "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/cosmos/cosmos-sdk/x/supply" + + "github.com/kava-labs/kava/x/auction" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/liquidator" + "github.com/kava-labs/kava/x/pricefeed" + validatorvesting "github.com/kava-labs/kava/x/validator-vesting" +) + +// TestApp is a simple wrapper around an App. It exposes internal keepers for use in integration tests. +// This file also contains test helpers. Ideally they would be in separate package. +// Basic Usage: +// Create a test app with NewTestApp, then all keepers and their methods can be accessed for test setup and execution. +// Advanced Usage: +// Some tests call for an app to be initialized with some state. This can be achieved through keeper method calls (ie keeper.SetParams(...)). +// However this leads to a lot of duplicated logic similar to InitGenesis methods. +// So TestApp.InitializeFromGenesisStates() will call InitGenesis with the default genesis state. +// and TestApp.InitializeFromGenesisStates(authState, cdpState) will do the same but overwrite the auth and cdp sections of the default genesis state +// Creating the genesis states can be combersome, but helper methods can make it easier such as NewAuthGenStateFromAccounts below. +type TestApp struct { + App +} + +func NewTestApp() TestApp { + db := tmdb.NewMemDB() + app := NewApp(log.NewNopLogger(), db, nil, true, 0) + return TestApp{App: *app} +} + +func (tApp TestApp) GetAccountKeeper() auth.AccountKeeper { return tApp.accountKeeper } +func (tApp TestApp) GetBankKeeper() bank.Keeper { return tApp.bankKeeper } +func (tApp TestApp) GetSupplyKeeper() supply.Keeper { return tApp.supplyKeeper } +func (tApp TestApp) GetStakingKeeper() staking.Keeper { return tApp.stakingKeeper } +func (tApp TestApp) GetSlashingKeeper() slashing.Keeper { return tApp.slashingKeeper } +func (tApp TestApp) GetMintKeeper() mint.Keeper { return tApp.mintKeeper } +func (tApp TestApp) GetDistrKeeper() distribution.Keeper { return tApp.distrKeeper } +func (tApp TestApp) GetGovKeeper() gov.Keeper { return tApp.govKeeper } +func (tApp TestApp) GetCrisisKeeper() crisis.Keeper { return tApp.crisisKeeper } +func (tApp TestApp) GetParamsKeeper() params.Keeper { return tApp.paramsKeeper } +func (tApp TestApp) GetVVKeeper() validatorvesting.Keeper { return tApp.vvKeeper } +func (tApp TestApp) GetAuctionKeeper() auction.Keeper { return tApp.auctionKeeper } +func (tApp TestApp) GetCDPKeeper() cdp.Keeper { return tApp.cdpKeeper } +func (tApp TestApp) GetLiquidatorKeeper() liquidator.Keeper { return tApp.liquidatorKeeper } +func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper } + +// This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states +func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp { + // Create a default genesis state and overwrite with provided values + genesisState := NewDefaultGenesisState() + for _, state := range genesisStates { + for k, v := range state { + genesisState[k] = v + } + } + + // Initialize the chain + stateBytes, err := codec.MarshalJSONIndent(tApp.cdc, genesisState) + if err != nil { + panic(err) + } + tApp.InitChain( + abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + }, + ) + tApp.Commit() + tApp.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: tApp.LastBlockHeight() + 1}}) + return tApp +} + +func (tApp TestApp) CheckBalance(t *testing.T, ctx sdk.Context, owner sdk.AccAddress, expectedCoins sdk.Coins) { + actualCoins := tApp.GetAccountKeeper().GetAccount(ctx, owner).GetCoins() + require.Equal(t, expectedCoins, actualCoins) +} + +// Create a new auth genesis state from some addresses and coins. The state is returned marshalled into a map. +func NewAuthGenState(addresses []sdk.AccAddress, coins []sdk.Coins) GenesisState { + // Create GenAccounts + accounts := authexported.GenesisAccounts{} + for i := range addresses { + accounts = append(accounts, auth.NewBaseAccount(addresses[i], coins[i], nil, 0, 0)) + } + // Create the auth genesis state + authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts) + return GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)} +} + +// GeneratePrivKeyAddressPairsFromRand generates (deterministically) a total of n private keys and addresses. +// TODO only generate secp256 keys? +func GeneratePrivKeyAddressPairs(n int) (keys []crypto.PrivKey, addrs []sdk.AccAddress) { + r := rand.New(rand.NewSource(12345)) // make the generation deterministic + keys = make([]crypto.PrivKey, n) + addrs = make([]sdk.AccAddress, n) + for i := 0; i < n; i++ { + secret := make([]byte, 32) + _, err := r.Read(secret) + if err != nil { + panic("Could not read randomness") + } + if r.Int63()%2 == 0 { + keys[i] = secp256k1.GenPrivKeySecp256k1(secret) + } else { + keys[i] = ed25519.GenPrivKeyFromSecret(secret) + } + addrs[i] = sdk.AccAddress(keys[i].PubKey().Address()) + } + return +} diff --git a/x/auction/abci_test.go b/x/auction/abci_test.go index 2fb36bda..38941aeb 100644 --- a/x/auction/abci_test.go +++ b/x/auction/abci_test.go @@ -1,4 +1,4 @@ -package auction +package auction_test import ( "testing" @@ -6,23 +6,43 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/auction" ) func TestKeeper_EndBlocker(t *testing.T) { - // setup keeper and auction - mapp, keeper, addresses, _ := setUpMockApp() - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(1) + seller := addrs[0] - seller := addresses[0] - keeper.StartForwardAuction(ctx, seller, sdk.NewInt64Coin("token1", 20), sdk.NewInt64Coin("token2", 0)) + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100))}), + ) - // run the endblocker, simulating a block height after auction expiry - expiryBlock := ctx.BlockHeight() + int64(DefaultMaxAuctionDuration) - EndBlocker(ctx.WithBlockHeight(expiryBlock), keeper) + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetAuctionKeeper() - // check auction has been closed - _, found := keeper.GetAuction(ctx, 0) + auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) + require.NoError(t, err) + + // Run the endblocker, simulating a block height just before auction expiry + preExpiryHeight := ctx.BlockHeight() + int64(auction.DefaultMaxAuctionDuration) - 1 + auction.EndBlocker(ctx.WithBlockHeight(preExpiryHeight), keeper) + + // Check auction has not been closed yet + _, found := keeper.GetAuction(ctx, auctionID) + require.True(t, found) + + // Run the endblocker, simulating a block height just after auction expiry + expiryHeight := preExpiryHeight + 1 + auction.EndBlocker(ctx.WithBlockHeight(expiryHeight), keeper) + + // Check auction has been closed + _, found = keeper.GetAuction(ctx, auctionID) require.False(t, found) } + +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } diff --git a/x/auction/app_test.go b/x/auction/app_test.go deleted file mode 100644 index 20106efb..00000000 --- a/x/auction/app_test.go +++ /dev/null @@ -1,171 +0,0 @@ -package auction - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" -) - -// TestApp contans several basic integration tests of creating an auction, placing a bid, and the auction closing. - -func TestApp_ForwardAuction(t *testing.T) { - // Setup - mapp, keeper, addresses, privKeys := setUpMockApp() - seller := addresses[0] - //sellerKey := privKeys[0] - buyer := addresses[1] - buyerKey := privKeys[1] - - // Create a block where an auction is started (lot: 20 t1, initialBid: 0 t2) - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) // make sure first arg is false, otherwise no db writes - keeper.StartForwardAuction(ctx, seller, sdk.NewInt64Coin("token1", 20), sdk.NewInt64Coin("token2", 0)) // lot, initialBid - mapp.EndBlock(abci.RequestEndBlock{}) - mapp.Commit() - - // Check seller's coins have decreased - mock.CheckBalance(t, mapp, seller, sdk.NewCoins(sdk.NewInt64Coin("token1", 80), sdk.NewInt64Coin("token2", 100))) - - // Deliver a block that contains a PlaceBid tx (bid: 10 t2, lot: same as starting) - msgs := []sdk.Msg{NewMsgPlaceBid(0, buyer, sdk.NewInt64Coin("token2", 10), sdk.NewInt64Coin("token1", 20))} // bid, lot - header = abci.Header{Height: mapp.LastBlockHeight() + 1} - mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, header, msgs, []uint64{1}, []uint64{0}, true, true, buyerKey) // account number for the buyer account is 1 - - // Check buyer's coins have decreased - mock.CheckBalance(t, mapp, buyer, sdk.NewCoins(sdk.NewInt64Coin("token1", 100), sdk.NewInt64Coin("token2", 90))) - // Check seller's coins have increased - mock.CheckBalance(t, mapp, seller, sdk.NewCoins(sdk.NewInt64Coin("token1", 80), sdk.NewInt64Coin("token2", 110))) - - // Deliver empty blocks until the auction should be closed (bid placed on block 3) - // TODO is there a way of skipping ahead? This takes a while and prints a lot. - for h := mapp.LastBlockHeight() + 1; h < int64(DefaultMaxBidDuration)+4; h++ { - mapp.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: h}}) - mapp.EndBlock(abci.RequestEndBlock{Height: h}) - mapp.Commit() - } - // Check buyer's coins increased - mock.CheckBalance(t, mapp, buyer, sdk.NewCoins(sdk.NewInt64Coin("token1", 120), sdk.NewInt64Coin("token2", 90))) -} - -func TestApp_ReverseAuction(t *testing.T) { - // Setup - mapp, keeper, addresses, privKeys := setUpMockApp() - seller := addresses[0] - sellerKey := privKeys[0] - buyer := addresses[1] - //buyerKey := privKeys[1] - - // Create a block where an auction is started - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) - keeper.StartReverseAuction(ctx, buyer, sdk.NewInt64Coin("token1", 20), sdk.NewInt64Coin("token2", 99)) // buyer, bid, initialLot - mapp.EndBlock(abci.RequestEndBlock{}) - mapp.Commit() - - // Check buyer's coins have decreased - mock.CheckBalance(t, mapp, buyer, sdk.NewCoins(sdk.NewInt64Coin("token1", 100), sdk.NewInt64Coin("token2", 1))) - - // Deliver a block that contains a PlaceBid tx - msgs := []sdk.Msg{NewMsgPlaceBid(0, seller, sdk.NewInt64Coin("token1", 20), sdk.NewInt64Coin("token2", 10))} // bid, lot - header = abci.Header{Height: mapp.LastBlockHeight() + 1} - mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, header, msgs, []uint64{0}, []uint64{0}, true, true, sellerKey) - - // Check seller's coins have decreased - mock.CheckBalance(t, mapp, seller, sdk.NewCoins(sdk.NewInt64Coin("token1", 80), sdk.NewInt64Coin("token2", 100))) - // Check buyer's coins have increased - mock.CheckBalance(t, mapp, buyer, sdk.NewCoins(sdk.NewInt64Coin("token1", 120), sdk.NewInt64Coin("token2", 90))) - - // Deliver empty blocks until the auction should be closed (bid placed on block 3) - for h := mapp.LastBlockHeight() + 1; h < int64(DefaultMaxBidDuration)+4; h++ { - mapp.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: h}}) - mapp.EndBlock(abci.RequestEndBlock{Height: h}) - mapp.Commit() - } - - // Check seller's coins increased - mock.CheckBalance(t, mapp, seller, sdk.NewCoins(sdk.NewInt64Coin("token1", 80), sdk.NewInt64Coin("token2", 110))) -} -func TestApp_ForwardReverseAuction(t *testing.T) { - // Setup - mapp, keeper, addresses, privKeys := setUpMockApp() - seller := addresses[0] - //sellerKey := privKeys[0] - buyer := addresses[1] - buyerKey := privKeys[1] - recipient := addresses[2] - - // Create a block where an auction is started - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) - keeper.StartForwardReverseAuction(ctx, seller, sdk.NewInt64Coin("token1", 20), sdk.NewInt64Coin("token2", 50), recipient) // seller, lot, maxBid, otherPerson - mapp.EndBlock(abci.RequestEndBlock{}) - mapp.Commit() - - // Check seller's coins have decreased - mock.CheckBalance(t, mapp, seller, sdk.NewCoins(sdk.NewInt64Coin("token1", 80), sdk.NewInt64Coin("token2", 100))) - - // Deliver a block that contains a PlaceBid tx - msgs := []sdk.Msg{NewMsgPlaceBid(0, buyer, sdk.NewInt64Coin("token2", 50), sdk.NewInt64Coin("token1", 15))} // bid, lot - header = abci.Header{Height: mapp.LastBlockHeight() + 1} - mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, header, msgs, []uint64{1}, []uint64{0}, true, true, buyerKey) - - // Check bidder's coins have decreased - mock.CheckBalance(t, mapp, buyer, sdk.NewCoins(sdk.NewInt64Coin("token1", 100), sdk.NewInt64Coin("token2", 50))) - // Check seller's coins have increased - mock.CheckBalance(t, mapp, seller, sdk.NewCoins(sdk.NewInt64Coin("token1", 80), sdk.NewInt64Coin("token2", 150))) - // Check "recipient" has received coins - mock.CheckBalance(t, mapp, recipient, sdk.NewCoins(sdk.NewInt64Coin("token1", 105), sdk.NewInt64Coin("token2", 100))) - - // Deliver empty blocks until the auction should be closed (bid placed on block 3) - for h := mapp.LastBlockHeight() + 1; h < int64(DefaultMaxBidDuration)+4; h++ { - mapp.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: h}}) - mapp.EndBlock(abci.RequestEndBlock{Height: h}) - mapp.Commit() - } - - // Check buyer's coins increased - mock.CheckBalance(t, mapp, buyer, sdk.NewCoins(sdk.NewInt64Coin("token1", 115), sdk.NewInt64Coin("token2", 50))) -} - -func setUpMockApp() (*mock.App, Keeper, []sdk.AccAddress, []crypto.PrivKey) { - // Create uninitialized mock app - mapp := mock.NewApp() - - // Register codecs - RegisterCodec(mapp.Cdc) - - // Create keepers - keyAuction := sdk.NewKVStoreKey("auction") - blacklistedAddrs := make(map[string]bool) - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) - auctionKeeper := NewKeeper(mapp.Cdc, bankKeeper, keyAuction, mapp.ParamsKeeper.Subspace(DefaultParamspace)) - - // Register routes - mapp.Router().AddRoute("auction", NewHandler(auctionKeeper)) - - // Add endblocker - mapp.SetEndBlocker( - func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - EndBlocker(ctx, auctionKeeper) - return abci.ResponseEndBlock{} - }, - ) - // Mount and load the stores - err := mapp.CompleteSetup(keyAuction) - if err != nil { - panic("mock app setup failed") - } - - // Create a bunch (ie 10) of pre-funded accounts to use for tests - genAccs, addrs, _, privKeys := mock.CreateGenAccounts(10, sdk.NewCoins(sdk.NewInt64Coin("token1", 100), sdk.NewInt64Coin("token2", 100))) - mock.SetGenesis(mapp, genAccs) - - return mapp, auctionKeeper, addrs, privKeys -} diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index 1ffc561b..f43f81cc 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -144,8 +144,8 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID types.ID) sdk.Error { return err } - // delete auction from store (and queue) - k.deleteAuction(ctx, auctionID) + // Delete auction from store (and queue) + k.DeleteAuction(ctx, auctionID) return nil } @@ -204,7 +204,7 @@ func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) { store.Set(k.getAuctionKey(auction.GetID()), bz) // add to the queue - k.insertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) + k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) } // getAuction gets an auction from the store by auctionID @@ -221,8 +221,8 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, return auction, true } -// deleteAuction removes an auction from the store without any validation -func (k Keeper) deleteAuction(ctx sdk.Context, auctionID types.ID) { +// DeleteAuction removes an auction from the store without any validation +func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { // remove from queue auction, found := k.GetAuction(ctx, auctionID) if found { @@ -245,7 +245,7 @@ func (k Keeper) getAuctionKey(auctionID types.ID) []byte { } // Inserts a AuctionID into the queue at endTime -func (k Keeper) insertIntoQueue(ctx sdk.Context, endTime types.EndTime, auctionID types.ID) { +func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime types.EndTime, auctionID types.ID) { // get the store store := ctx.KVStore(k.storeKey) // marshal thing to be inserted diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index 6c77b32e..873dcb81 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -1,21 +1,129 @@ -package keeper +package keeper_test import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/auction/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/auction/keeper" + "github.com/kava-labs/kava/x/auction/types" ) +func TestKeeper_ForwardAuction(t *testing.T) { + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(2) + seller := addrs[0] + buyer := addrs[1] + + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), + ) + + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetAuctionKeeper() + + // Create an auction (lot: 20 t1, initialBid: 0 t2) + auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) // lot, initialBid + require.NoError(t, err) + // Check seller's coins have decreased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) + + // PlaceBid (bid: 10 t2, lot: same as starting) + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot + // Check buyer's coins have decreased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) + // Check seller's coins have increased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) + + // Close auction at just after auction expiry + ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) + require.NoError(t, keeper.CloseAuction(ctx, auctionID)) + // Check buyer's coins increased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) +} + +func TestKeeper_ReverseAuction(t *testing.T) { + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(2) + seller := addrs[0] + buyer := addrs[1] + + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), + ) + + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetAuctionKeeper() + + // Start auction + auctionID, err := keeper.StartReverseAuction(ctx, buyer, c("token1", 20), c("token2", 99)) // buyer, bid, initialLot + require.NoError(t, err) + // Check buyer's coins have decreased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 1))) + + // Place a bid + require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot + // Check seller's coins have decreased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) + // Check buyer's coins have increased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) + + // Close auction at just after auction expiry + ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) + require.NoError(t, keeper.CloseAuction(ctx, auctionID)) + // Check seller's coins increased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) +} + +func TestKeeper_ForwardReverseAuction(t *testing.T) { + // Setup + _, addrs := app.GeneratePrivKeyAddressPairs(3) + seller := addrs[0] + buyer := addrs[1] + recipient := addrs[2] + + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), + ) + + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetAuctionKeeper() + + // Start auction + auctionID, err := keeper.StartForwardReverseAuction(ctx, seller, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson + require.NoError(t, err) + // Check seller's coins have decreased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) + + // Place a bid + require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot + // Check bidder's coins have decreased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) + // Check seller's coins have increased + tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 150))) + // Check "recipient" has received coins + tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) + + // Close auction at just after auction expiry + ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) + require.NoError(t, keeper.CloseAuction(ctx, auctionID)) + // Check buyer's coins increased + tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) +} + func TestKeeper_SetGetDeleteAuction(t *testing.T) { // setup keeper, create auction - mapp, keeper, addresses, _ := setUpMockApp() - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) // Without this it panics about "invalid memory address or nil pointer dereference" - ctx := mapp.BaseApp.NewContext(false, header) - auction, _ := types.NewForwardAuction(addresses[0], sdk.NewInt64Coin("usdx", 100), sdk.NewInt64Coin("kava", 0), types.EndTime(1000)) + _, addrs := app.GeneratePrivKeyAddressPairs(1) + tApp := app.NewTestApp() + keeper := tApp.GetAuctionKeeper() + ctx := tApp.NewContext(true, abci.Header{}) + auction, _ := types.NewForwardAuction(addrs[0], c("usdx", 100), c("kava", 0), types.EndTime(1000)) id := types.ID(5) auction.SetID(id) @@ -26,15 +134,13 @@ func TestKeeper_SetGetDeleteAuction(t *testing.T) { // check before and after match require.True(t, found) require.Equal(t, &auction, readAuction) - t.Log(auction) - t.Log(readAuction.GetID()) // check auction is in queue iter := keeper.GetQueueIterator(ctx, 100000) require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter))) iter.Close() // delete auction - keeper.deleteAuction(ctx, id) + keeper.DeleteAuction(ctx, id) // check auction does not exist _, found = keeper.GetAuction(ctx, id) @@ -49,10 +155,10 @@ func TestKeeper_SetGetDeleteAuction(t *testing.T) { // TODO convert to table driven test with more test cases func TestKeeper_ExpiredAuctionQueue(t *testing.T) { // setup keeper - mapp, keeper, _, _ := setUpMockApp() - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) + tApp := app.NewTestApp() + keeper := tApp.GetAuctionKeeper() + ctx := tApp.NewContext(true, abci.Header{}) + // create an example queue type queue []struct { endTime types.EndTime @@ -62,7 +168,7 @@ func TestKeeper_ExpiredAuctionQueue(t *testing.T) { // write and read queue for _, v := range q { - keeper.insertIntoQueue(ctx, v.endTime, v.auctionID) + keeper.InsertIntoQueue(ctx, v.endTime, v.auctionID) } iter := keeper.GetQueueIterator(ctx, 1000) @@ -70,19 +176,22 @@ func TestKeeper_ExpiredAuctionQueue(t *testing.T) { i := 0 for ; iter.Valid(); iter.Next() { var auctionID types.ID - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &auctionID) + tApp.Codec().MustUnmarshalBinaryLengthPrefixed(iter.Value(), &auctionID) require.Equal(t, q[i].auctionID, auctionID) i++ } } -func convertIteratorToSlice(keeper Keeper, iterator sdk.Iterator) []types.ID { +func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types.ID { var queue []types.ID for ; iterator.Valid(); iterator.Next() { var auctionID types.ID - keeper.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auctionID) + types.ModuleCdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auctionID) queue = append(queue, auctionID) } return queue } + +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } diff --git a/x/auction/keeper/test_common.go b/x/auction/keeper/test_common.go deleted file mode 100644 index b5702438..00000000 --- a/x/auction/keeper/test_common.go +++ /dev/null @@ -1,35 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/kava-labs/kava/x/auction/types" - "github.com/tendermint/tendermint/crypto" -) - -func setUpMockApp() (*mock.App, Keeper, []sdk.AccAddress, []crypto.PrivKey) { - // Create uninitialized mock app - mapp := mock.NewApp() - - // Register codecs - types.RegisterCodec(mapp.Cdc) - - // Create keepers - keyAuction := sdk.NewKVStoreKey("auction") - blacklistedAddrs := make(map[string]bool) - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, mapp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) - auctionKeeper := NewKeeper(mapp.Cdc, bankKeeper, keyAuction, mapp.ParamsKeeper.Subspace(types.DefaultParamspace)) - - // Mount and load the stores - err := mapp.CompleteSetup(keyAuction) - if err != nil { - panic("mock app setup failed") - } - - // Create a bunch (ie 10) of pre-funded accounts to use for tests - genAccs, addrs, _, privKeys := mock.CreateGenAccounts(10, sdk.NewCoins(sdk.NewInt64Coin("token1", 100), sdk.NewInt64Coin("token2", 100))) - mock.SetGenesis(mapp, genAccs) - - return mapp, auctionKeeper, addrs, privKeys -} diff --git a/x/cdp/app_test.go b/x/cdp/app_test.go index 7eac72ee..a11758ee 100644 --- a/x/cdp/app_test.go +++ b/x/cdp/app_test.go @@ -1,73 +1,55 @@ -package cdp +package cdp_test import ( "testing" - "time" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mock" abci "github.com/tendermint/tendermint/abci/types" - tmtime "github.com/tendermint/tendermint/types/time" - "github.com/kava-labs/kava/x/pricefeed" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" ) func TestApp_CreateModifyDeleteCDP(t *testing.T) { // Setup - mapp, keeper, pfKeeper := setUpMockAppWithoutGenesis() - genAccs, addrs, _, privKeys := mock.CreateGenAccounts(1, cs(c("xrp", 100))) + tApp := app.NewTestApp() + privKeys, addrs := app.GeneratePrivKeyAddressPairs(1) testAddr := addrs[0] testPrivKey := privKeys[0] - mock.SetGenesis(mapp, genAccs) - mock.CheckBalance(t, mapp, testAddr, cs(c("xrp", 100))) - // setup pricefeed, TODO can this be shortened a bit? - header := abci.Header{Height: mapp.LastBlockHeight() + 1, Time: tmtime.Now()} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) - params := CdpParams{ - GlobalDebtLimit: sdk.NewInt(100000), - CollateralParams: []CollateralParams{ - { - Denom: "xrp", - LiquidationRatio: sdk.MustNewDecFromStr("1.5"), - DebtLimit: sdk.NewInt(10000), - }, - }, - StableDenoms: []string{"usdx"}, - } - keeper.SetParams(ctx, params) - keeper.SetGlobalDebt(ctx, sdk.NewInt(0)) - ap := pricefeed.Params{ - Markets: []pricefeed.Market{ - pricefeed.Market{ - MarketID: "xrp", BaseAsset: "xrp", - QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, - }, - } - pfKeeper.SetParams(ctx, ap) - pfKeeper.SetPrice( - ctx, sdk.AccAddress{}, "xrp", - sdk.MustNewDecFromStr("1.00"), - header.Time.Add(time.Hour*1)) - pfKeeper.SetCurrentPrices(ctx, "xrp") - mapp.EndBlock(abci.RequestEndBlock{}) - mapp.Commit() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("xrp", 100))}), + NewPFGenState("xrp", d("1.00")), + NewCDPGenState("xrp", d("1.5")), + ) + // check balance + ctx := tApp.NewContext(false, abci.Header{}) + tApp.CheckBalance(t, ctx, testAddr, cs(c("xrp", 100))) + + tApp.EndBlock(abci.RequestEndBlock{}) + tApp.Commit() // Create CDP - msgs := []sdk.Msg{NewMsgCreateOrModifyCDP(testAddr, "xrp", i(10), i(5))} - mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, abci.Header{Height: mapp.LastBlockHeight() + 1}, msgs, []uint64{0}, []uint64{0}, true, true, testPrivKey) + msgs := []sdk.Msg{cdp.NewMsgCreateOrModifyCDP(testAddr, "xrp", i(10), i(5))} + simapp.SignCheckDeliver(t, tApp.Codec(), tApp.BaseApp, abci.Header{Height: tApp.LastBlockHeight() + 1}, msgs, []uint64{0}, []uint64{0}, true, true, testPrivKey) - mock.CheckBalance(t, mapp, testAddr, cs(c("usdx", 5), c("xrp", 90))) + // check balance + ctx = tApp.NewContext(true, abci.Header{}) + tApp.CheckBalance(t, ctx, testAddr, cs(c("usdx", 5), c("xrp", 90))) // Modify CDP - msgs = []sdk.Msg{NewMsgCreateOrModifyCDP(testAddr, "xrp", i(40), i(5))} - mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, abci.Header{Height: mapp.LastBlockHeight() + 1}, msgs, []uint64{0}, []uint64{1}, true, true, testPrivKey) + msgs = []sdk.Msg{cdp.NewMsgCreateOrModifyCDP(testAddr, "xrp", i(40), i(5))} + simapp.SignCheckDeliver(t, tApp.Codec(), tApp.BaseApp, abci.Header{Height: tApp.LastBlockHeight() + 1}, msgs, []uint64{0}, []uint64{1}, true, true, testPrivKey) - mock.CheckBalance(t, mapp, testAddr, cs(c("usdx", 10), c("xrp", 50))) + // check balance + ctx = tApp.NewContext(true, abci.Header{}) + tApp.CheckBalance(t, ctx, testAddr, cs(c("usdx", 10), c("xrp", 50))) // Delete CDP - msgs = []sdk.Msg{NewMsgCreateOrModifyCDP(testAddr, "xrp", i(-50), i(-10))} - mock.SignCheckDeliver(t, mapp.Cdc, mapp.BaseApp, abci.Header{Height: mapp.LastBlockHeight() + 1}, msgs, []uint64{0}, []uint64{2}, true, true, testPrivKey) + msgs = []sdk.Msg{cdp.NewMsgCreateOrModifyCDP(testAddr, "xrp", i(-50), i(-10))} + simapp.SignCheckDeliver(t, tApp.Codec(), tApp.BaseApp, abci.Header{Height: tApp.LastBlockHeight() + 1}, msgs, []uint64{0}, []uint64{2}, true, true, testPrivKey) - mock.CheckBalance(t, mapp, testAddr, cs(c("xrp", 100))) + // check balance + ctx = tApp.NewContext(true, abci.Header{}) + tApp.CheckBalance(t, ctx, testAddr, cs(c("xrp", 100))) } diff --git a/x/cdp/integration_test.go b/x/cdp/integration_test.go new file mode 100644 index 00000000..abf674fb --- /dev/null +++ b/x/cdp/integration_test.go @@ -0,0 +1,56 @@ +package cdp_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/pricefeed" +) + +// Avoid cluttering test cases with long function name +func i(in int64) sdk.Int { return sdk.NewInt(in) } +func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +func NewPFGenState(asset string, price sdk.Dec) app.GenesisState { + quote := "usd" + ap := pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: asset, BaseAsset: asset, QuoteAsset: quote, Oracles: pricefeed.Oracles{}, Active: true}, + }, + } + pfGenesis := pricefeed.GenesisState{ + Params: ap, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: asset, + OracleAddress: sdk.AccAddress{}, + Price: price, + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} + +func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { + cdpGenesis := cdp.GenesisState{ + Params: cdp.CdpParams{ + GlobalDebtLimit: sdk.NewInt(1000000), + CollateralParams: []cdp.CollateralParams{ + { + Denom: asset, + LiquidationRatio: liquidationRatio, + DebtLimit: sdk.NewInt(500000), + }, + }, + }, + GlobalDebt: sdk.ZeroInt(), + CDPs: cdp.CDPs{}, + } + return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} +} diff --git a/x/cdp/keeper/bank_test.go b/x/cdp/keeper/bank_test.go index ddac5479..3e13b4b4 100644 --- a/x/cdp/keeper/bank_test.go +++ b/x/cdp/keeper/bank_test.go @@ -1,19 +1,19 @@ -package keeper +package keeper_test import ( "testing" 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/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + + "github.com/kava-labs/kava/app" + keep "github.com/kava-labs/kava/x/cdp/keeper" ) // Test the bank functionality of the CDP keeper func TestKeeper_AddSubtractGetCoins(t *testing.T) { - _, addrs := mock.GeneratePrivKeyAddressPairs(1) + _, addrs := app.GeneratePrivKeyAddressPairs(1) normalAddr := addrs[0] tests := []struct { @@ -25,28 +25,24 @@ func TestKeeper_AddSubtractGetCoins(t *testing.T) { }{ {"addNormalAddress", normalAddr, true, cs(c("usdx", 53)), cs(c("usdx", 153), c("kava", 100))}, {"subNormalAddress", normalAddr, false, cs(c("usdx", 53)), cs(c("usdx", 47), c("kava", 100))}, - {"addLiquidatorStable", LiquidatorAccountAddress, true, cs(c("usdx", 53)), cs(c("usdx", 153))}, - {"subLiquidatorStable", LiquidatorAccountAddress, false, cs(c("usdx", 53)), cs(c("usdx", 47))}, - {"addLiquidatorGov", LiquidatorAccountAddress, true, cs(c("kava", 53)), cs(c("usdx", 100))}, // no change to balance - {"subLiquidatorGov", LiquidatorAccountAddress, false, cs(c("kava", 53)), cs(c("usdx", 100))}, // no change to balance + {"addLiquidatorStable", keep.LiquidatorAccountAddress, true, cs(c("usdx", 53)), cs(c("usdx", 153))}, + {"subLiquidatorStable", keep.LiquidatorAccountAddress, false, cs(c("usdx", 53)), cs(c("usdx", 47))}, + {"addLiquidatorGov", keep.LiquidatorAccountAddress, true, cs(c("kava", 53)), cs(c("usdx", 100))}, // no change to balance + {"subLiquidatorGov", keep.LiquidatorAccountAddress, false, cs(c("kava", 53)), cs(c("usdx", 100))}, // no change to balance } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - // setup keeper - mapp, keeper, _, _ := setUpMockAppWithoutGenesis() - // initialize an account with coins - genAcc := auth.BaseAccount{ - Address: normalAddr, - Coins: cs(c("usdx", 100), c("kava", 100)), - } - mock.SetGenesis(mapp, []authexported.Account{&genAcc}) + // setup app with an account + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState([]sdk.AccAddress{normalAddr}, []sdk.Coins{cs(c("usdx", 100), c("kava", 100))}), + ) // create a new context and setup the liquidator account - header := abci.Header{Height: mapp.LastBlockHeight() + 1} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) - keeper.setLiquidatorModuleAccount(ctx, LiquidatorModuleAccount{cs(c("usdx", 100))}) // set gov coin "balance" to zero + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetCDPKeeper() + keeper.SetLiquidatorModuleAccount(ctx, keep.LiquidatorModuleAccount{cs(c("usdx", 100))}) // set gov coin "balance" to zero // perform the test action var err sdk.Error @@ -56,9 +52,6 @@ func TestKeeper_AddSubtractGetCoins(t *testing.T) { _, err = keeper.SubtractCoins(ctx, tc.address, tc.amount) } - mapp.EndBlock(abci.RequestEndBlock{}) - mapp.Commit() - // check balances are as expected require.NoError(t, err) require.Equal(t, tc.expectedCoins, keeper.GetCoins(ctx, tc.address)) diff --git a/x/cdp/keeper/integration_test.go b/x/cdp/keeper/integration_test.go new file mode 100644 index 00000000..23fd6722 --- /dev/null +++ b/x/cdp/keeper/integration_test.go @@ -0,0 +1,103 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/cdp/types" + "github.com/kava-labs/kava/x/pricefeed" +) + +// Avoid cluttering test cases with long function name +func i(in int64) sdk.Int { return sdk.NewInt(in) } +func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState { + pfGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: asset, BaseAsset: asset, QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: asset, + OracleAddress: sdk.AccAddress{}, + Price: price, + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} + +func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { + cdpGenesis := cdp.GenesisState{ + Params: cdp.CdpParams{ + GlobalDebtLimit: sdk.NewInt(1000000), + CollateralParams: []cdp.CollateralParams{ + { + Denom: asset, + LiquidationRatio: liquidationRatio, + DebtLimit: sdk.NewInt(500000), + }, + }, + }, + GlobalDebt: sdk.ZeroInt(), + CDPs: cdp.CDPs{}, + } + return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} +} + +func NewPricefeedGenStateMulti() app.GenesisState { + pfGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "btc", BaseAsset: "btc", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + pricefeed.Market{MarketID: "xrp", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: "btc", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("8000.00"), + Expiry: time.Now().Add(1 * time.Hour), + }, + pricefeed.PostedPrice{ + MarketID: "xrp", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("0.25"), + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} +func NewCDPGenStateMulti() app.GenesisState { + cdpGenesis := cdp.GenesisState{ + Params: cdp.CdpParams{ + GlobalDebtLimit: sdk.NewInt(1000000), + CollateralParams: []types.CollateralParams{ + { + Denom: "btc", + LiquidationRatio: sdk.MustNewDecFromStr("1.5"), + DebtLimit: sdk.NewInt(500000), + }, + { + Denom: "xrp", + LiquidationRatio: sdk.MustNewDecFromStr("2.0"), + DebtLimit: sdk.NewInt(500000), + }, + }, + }, + GlobalDebt: sdk.ZeroInt(), + CDPs: cdp.CDPs{}, + } + return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} +} diff --git a/x/cdp/keeper/keeper.go b/x/cdp/keeper/keeper.go index ee9f2818..83774e52 100644 --- a/x/cdp/keeper/keeper.go +++ b/x/cdp/keeper/keeper.go @@ -126,13 +126,13 @@ func (k Keeper) ModifyCDP(ctx sdk.Context, owner sdk.AccAddress, collateralDenom } // Set CDP if cdp.CollateralAmount.IsZero() && cdp.Debt.IsZero() { // TODO maybe abstract this logic into SetCDP - k.deleteCDP(ctx, cdp) + k.DeleteCDP(ctx, cdp) } else { k.SetCDP(ctx, cdp) } // set total debts k.SetGlobalDebt(ctx, gDebt) - k.setCollateralState(ctx, collateralState) + k.SetCollateralState(ctx, collateralState) return nil } @@ -195,11 +195,11 @@ func (k Keeper) PartialSeizeCDP(ctx sdk.Context, owner sdk.AccAddress, collatera // Store updated state if cdp.CollateralAmount.IsZero() && cdp.Debt.IsZero() { // TODO maybe abstract this logic into SetCDP - k.deleteCDP(ctx, cdp) + k.DeleteCDP(ctx, cdp) } else { k.SetCDP(ctx, cdp) } - k.setCollateralState(ctx, collateralState) + k.SetCollateralState(ctx, collateralState) return nil } @@ -264,7 +264,7 @@ func (k Keeper) SetCDP(ctx sdk.Context, cdp types.CDP) { bz := k.cdc.MustMarshalBinaryLengthPrefixed(cdp) store.Set(k.getCDPKey(cdp.Owner, cdp.CollateralDenom), bz) } -func (k Keeper) deleteCDP(ctx sdk.Context, cdp types.CDP) { // TODO should this id the cdp by passing in owner,collateralDenom pair? +func (k Keeper) DeleteCDP(ctx sdk.Context, cdp types.CDP) { // TODO should this id the cdp by passing in owner,collateralDenom pair? // get store store := ctx.KVStore(k.key) // delete key @@ -354,7 +354,7 @@ func (k Keeper) GetCollateralState(ctx sdk.Context, collateralDenom string) (typ k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &collateralState) return collateralState, true } -func (k Keeper) setCollateralState(ctx sdk.Context, collateralstate types.CollateralState) { +func (k Keeper) SetCollateralState(ctx sdk.Context, collateralstate types.CollateralState) { // get store store := ctx.KVStore(k.key) // marshal and set @@ -422,7 +422,7 @@ func (k Keeper) AddCoins(ctx sdk.Context, address sdk.AccAddress, amount sdk.Coi return amount, sdk.ErrInsufficientCoins(fmt.Sprintf("insufficient account funds; %s < %s", lma.Coins, amount)) } lma.Coins = updatedCoins - k.setLiquidatorModuleAccount(ctx, lma) + k.SetLiquidatorModuleAccount(ctx, lma) return updatedCoins, nil } else { return k.bankKeeper.AddCoins(ctx, address, amount) @@ -445,7 +445,7 @@ func (k Keeper) SubtractCoins(ctx sdk.Context, address sdk.AccAddress, amount sd return amount, sdk.ErrInsufficientCoins(fmt.Sprintf("insufficient account funds; %s < %s", lma.Coins, amount)) } lma.Coins = updatedCoins - k.setLiquidatorModuleAccount(ctx, lma) + k.SetLiquidatorModuleAccount(ctx, lma) return updatedCoins, nil } else { return k.bankKeeper.SubtractCoins(ctx, address, amount) @@ -483,7 +483,7 @@ func (k Keeper) getLiquidatorModuleAccount(ctx sdk.Context) LiquidatorModuleAcco k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &lma) return lma } -func (k Keeper) setLiquidatorModuleAccount(ctx sdk.Context, lma LiquidatorModuleAccount) { +func (k Keeper) SetLiquidatorModuleAccount(ctx sdk.Context, lma LiquidatorModuleAccount) { store := ctx.KVStore(k.key) bz := k.cdc.MustMarshalBinaryLengthPrefixed(lma) store.Set(liquidatorAccountKey, bz) diff --git a/x/cdp/keeper/keeper_test.go b/x/cdp/keeper/keeper_test.go index ebdd855f..35f2b696 100644 --- a/x/cdp/keeper/keeper_test.go +++ b/x/cdp/keeper/keeper_test.go @@ -1,4 +1,4 @@ -package keeper +package keeper_test import ( "fmt" @@ -6,24 +6,21 @@ import ( "time" 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" - tmtime "github.com/tendermint/tendermint/types/time" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp/types" ) // 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) + _, addrs := app.GeneratePrivKeyAddressPairs(1) ownerAddr := addrs[0] - type state struct { // TODO this allows invalid state to be set up, should it? + type state struct { CDP types.CDP OwnerCoins sdk.Coins GlobalDebt sdk.Int @@ -96,59 +93,32 @@ func TestKeeper_ModifyCDP(t *testing.T) { } 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 - header := abci.Header{Height: mapp.LastBlockHeight() + 1, Time: tmtime.Now()} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) - keeper.SetParams(ctx, defaultParamsSingle()) + // setup test app + tApp := app.NewTestApp() + // initialize cdp owner account with coins, and collateral with price and params + tApp.InitializeFromGenesisStates( + app.NewAuthGenState([]sdk.AccAddress{ownerAddr}, []sdk.Coins{tc.priorState.OwnerCoins}), + NewPricefeedGenState("xrp", d(tc.price)), + NewCDPGenState("xrp", d("2.0")), + ) + // create a context for db access + ctx := tApp.NewContext(false, abci.Header{}) + // setup store state - ap := pricefeed.Params{ - Markets: []pricefeed.Market{ - pricefeed.Market{ - MarketID: "xrp", BaseAsset: "xrp", - QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, - }, - } - keeper.pricefeedKeeper.SetParams(ctx, ap) - _, err := keeper.pricefeedKeeper.SetPrice( - ctx, ownerAddr, "xrp", - sdk.MustNewDecFromStr(tc.price), - header.Time.Add(time.Hour*1)) - if err != nil { - t.Log("test context height", ctx.BlockHeight()) - t.Log(err) - t.Log(tc.name) - } - err = keeper.pricefeedKeeper.SetCurrentPrices(ctx, "xrp") - if err != nil { - t.Log("test context height", ctx.BlockHeight()) - t.Log(err) - t.Log(tc.name) - } + keeper := tApp.GetCDPKeeper() 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) + 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() + err := keeper.ModifyCDP(ctx, tc.args.owner, tc.args.collateralDenom, tc.args.changeInCollateral, tc.args.changeInDebt) // 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)) @@ -167,47 +137,35 @@ func TestKeeper_ModifyCDP(t *testing.T) { 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) + tApp.CheckBalance(t, ctx, 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))) + _, addrs := app.GeneratePrivKeyAddressPairs(1) testAddr := addrs[0] - mock.SetGenesis(mapp, genAccs) - // setup pricefeed - header := abci.Header{Height: mapp.LastBlockHeight() + 1, Time: tmtime.Now()} - mapp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := mapp.BaseApp.NewContext(false, header) - keeper.SetParams(ctx, defaultParamsSingle()) - ap := pricefeed.Params{ - Markets: []pricefeed.Market{ - pricefeed.Market{ - MarketID: "xrp", BaseAsset: "xrp", - QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}, - }, - } - keeper.pricefeedKeeper.SetParams(ctx, ap) - keeper.pricefeedKeeper.SetPrice( - ctx, sdk.AccAddress{}, collateral, - sdk.MustNewDecFromStr("1.00"), - header.Time.Add(time.Hour*1)) - keeper.pricefeedKeeper.SetCurrentPrices(ctx, collateral) + + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c(collateral, 100))}), + NewPricefeedGenState(collateral, d("1.00")), + NewCDPGenState(collateral, d("2.00")), + ) + + ctx := tApp.NewContext(false, abci.Header{}) + keeper := tApp.GetCDPKeeper() + // 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( + tApp.GetPriceFeedKeeper().SetPrice( ctx, sdk.AccAddress{}, collateral, - sdk.MustNewDecFromStr("0.90"), - header.Time.Add(time.Hour*1)) - keeper.pricefeedKeeper.SetCurrentPrices(ctx, collateral) + d("0.90"), time.Now().Add(1*time.Hour)) + tApp.GetPriceFeedKeeper().SetCurrentPrices(ctx, collateral) // Seize entire CDP err = keeper.PartialSeizeCDP(ctx, testAddr, collateral, i(10), i(5)) @@ -222,15 +180,16 @@ func TestKeeper_PartialSeizeCDP(t *testing.T) { } 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 test app + tApp := app.NewTestApp().InitializeFromGenesisStates( + NewPricefeedGenStateMulti(), // collateral needs to be in pricefeed for cdp InitGenesis to validate + NewCDPGenStateMulti(), + ) + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetCDPKeeper() + // setup CDPs - _, addrs := mock.GeneratePrivKeyAddressPairs(2) + _, addrs := app.GeneratePrivKeyAddressPairs(2) cdps := types.CDPs{ {addrs[0], "xrp", i(4000), i(5)}, {addrs[1], "xrp", i(4000), i(2000)}, @@ -288,7 +247,7 @@ func TestKeeper_GetCDPs(t *testing.T) { _, err = keeper.GetCDPs(ctx, "", d("0.34023")) require.Error(t, err) // Check deleting a CDP removes it - keeper.deleteCDP(ctx, cdps[0]) + keeper.DeleteCDP(ctx, cdps[0]) returnedCdps, err = keeper.GetCDPs(ctx, "", sdk.Dec{}) require.NoError(t, err) require.Equal(t, @@ -298,14 +257,14 @@ func TestKeeper_GetCDPs(t *testing.T) { 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) + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetCDPKeeper() + + _, addrs := app.GeneratePrivKeyAddressPairs(1) cdp := types.CDP{addrs[0], "xrp", i(412), i(56)} // write and read from store @@ -317,7 +276,7 @@ func TestKeeper_GetSetDeleteCDP(t *testing.T) { require.Equal(t, cdp, readCDP) // delete auction - keeper.deleteCDP(ctx, cdp) + keeper.DeleteCDP(ctx, cdp) // check auction does not exist _, found = keeper.GetCDP(ctx, cdp.Owner, cdp.CollateralDenom) @@ -325,11 +284,10 @@ func TestKeeper_GetSetDeleteCDP(t *testing.T) { } 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()) + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetCDPKeeper() + gDebt := i(4120000) // write and read from store @@ -342,15 +300,14 @@ func TestKeeper_GetSetGDebt(t *testing.T) { 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()) + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetCDPKeeper() + collateralState := types.CollateralState{"xrp", i(15400)} // write and read from store - keeper.setCollateralState(ctx, collateralState) + keeper.SetCollateralState(ctx, collateralState) readCState, found := keeper.GetCollateralState(ctx, collateralState.Denom) // check before and after match diff --git a/x/cdp/keeper/test_common.go b/x/cdp/keeper/test_common.go deleted file mode 100644 index 59619e51..00000000 --- a/x/cdp/keeper/test_common.go +++ /dev/null @@ -1,88 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" - - "github.com/kava-labs/kava/x/cdp/types" - "github.com/kava-labs/kava/x/pricefeed" - "github.com/tendermint/tendermint/crypto" -) - -// Mock app is an ABCI app with an in memory database. -// This function creates an app, setting up the keepers, routes, begin and end blockers. -// But leaves it to the tests to call InitChain (done by calling mock.SetGenesis) -// The app works by submitting ABCI messages. -// - InitChain sets up the app db from genesis. -// - BeginBlock starts the delivery of a new block -// - DeliverTx delivers a tx -// - EndBlock signals the end of a block -// - Commit ? -func setUpMockAppWithoutGenesis() (*mock.App, Keeper, []sdk.AccAddress, []crypto.PrivKey) { - // Create uninitialized mock app - mapp := mock.NewApp() - - // Register codecs - types.RegisterCodec(mapp.Cdc) - - // Create keepers - keyCDP := sdk.NewKVStoreKey("cdp") - keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) - pk := mapp.ParamsKeeper - priceFeedKeeper := pricefeed.NewKeeper(keyPriceFeed, mapp.Cdc, pk.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace) - blacklistedAddrs := make(map[string]bool) - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) - cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(types.DefaultParamspace), priceFeedKeeper, bankKeeper) - - // Mount and load the stores - err := mapp.CompleteSetup(keyPriceFeed, keyCDP) - if err != nil { - panic("mock app setup failed") - } - - // Create a bunch (ie 10) of pre-funded accounts to use for tests - genAccs, addrs, _, privKeys := mock.CreateGenAccounts(10, sdk.NewCoins(sdk.NewInt64Coin("token1", 100), sdk.NewInt64Coin("token2", 100))) - mock.SetGenesis(mapp, genAccs) - - return mapp, cdpKeeper, addrs, privKeys -} - -// Avoid cluttering test cases with long function name -func i(in int64) sdk.Int { return sdk.NewInt(in) } -func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } -func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } -func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } - -func defaultParamsMulti() types.CdpParams { - return types.CdpParams{ - GlobalDebtLimit: sdk.NewInt(1000000), - CollateralParams: []types.CollateralParams{ - { - Denom: "btc", - LiquidationRatio: sdk.MustNewDecFromStr("1.5"), - DebtLimit: sdk.NewInt(500000), - }, - { - Denom: "xrp", - LiquidationRatio: sdk.MustNewDecFromStr("2.0"), - DebtLimit: sdk.NewInt(500000), - }, - }, - StableDenoms: []string{"usdx"}, - } -} - -func defaultParamsSingle() types.CdpParams { - return types.CdpParams{ - GlobalDebtLimit: sdk.NewInt(1000000), - CollateralParams: []types.CollateralParams{ - { - Denom: "xrp", - LiquidationRatio: sdk.MustNewDecFromStr("2.0"), - DebtLimit: sdk.NewInt(500000), - }, - }, - StableDenoms: []string{"usdx"}, - } -} diff --git a/x/cdp/test_common.go b/x/cdp/test_common.go deleted file mode 100644 index 5d537315..00000000 --- a/x/cdp/test_common.go +++ /dev/null @@ -1,52 +0,0 @@ -package cdp - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" - - "github.com/kava-labs/kava/x/pricefeed" -) - -// Mock app is an ABCI app with an in memory database. -// This function creates an app, setting up the keepers, routes, begin and end blockers. -// But leaves it to the tests to call InitChain (done by calling mock.SetGenesis) -// The app works by submitting ABCI messages. -// - InitChain sets up the app db from genesis. -// - BeginBlock starts the delivery of a new block -// - DeliverTx delivers a tx -// - EndBlock signals the end of a block -// - Commit ? -func setUpMockAppWithoutGenesis() (*mock.App, Keeper, PricefeedKeeper) { - // Create uninitialized mock app - mapp := mock.NewApp() - - // Register codecs - RegisterCodec(mapp.Cdc) - - // Create keepers - keyCDP := sdk.NewKVStoreKey("cdp") - keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) - pk := mapp.ParamsKeeper - priceFeedKeeper := pricefeed.NewKeeper(keyPriceFeed, mapp.Cdc, pk.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace) - blacklistedAddrs := make(map[string]bool) - bankKeeper := bank.NewBaseKeeper(mapp.AccountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) - cdpKeeper := NewKeeper(mapp.Cdc, keyCDP, pk.Subspace(DefaultParamspace), priceFeedKeeper, bankKeeper) - - // Register routes - mapp.Router().AddRoute("cdp", NewHandler(cdpKeeper)) - - // Mount and load the stores - err := mapp.CompleteSetup(keyPriceFeed, keyCDP) - if err != nil { - panic("mock app setup failed") - } - - return mapp, cdpKeeper, priceFeedKeeper -} - -// Avoid cluttering test cases with long function name -func i(in int64) sdk.Int { return sdk.NewInt(in) } -func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } -func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } -func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } diff --git a/x/liquidator/keeper/integration_test.go b/x/liquidator/keeper/integration_test.go new file mode 100644 index 00000000..53f936ac --- /dev/null +++ b/x/liquidator/keeper/integration_test.go @@ -0,0 +1,72 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/liquidator" + "github.com/kava-labs/kava/x/liquidator/types" + "github.com/kava-labs/kava/x/pricefeed" +) + +// Avoid cluttering test cases with long function name +func i(in int64) sdk.Int { return sdk.NewInt(in) } +func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } + +// Genesis states to initialize test apps + +func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState { + pfGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: asset, BaseAsset: asset, QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}}, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: asset, + OracleAddress: sdk.AccAddress{}, + Price: price, + Expiry: time.Now().Add(1 * time.Hour), + }, + }, + } + return app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pfGenesis)} +} + +func NewCDPGenState() app.GenesisState { + cdpGenesis := cdp.GenesisState{ + Params: cdp.CdpParams{ + GlobalDebtLimit: sdk.NewInt(1000000), + CollateralParams: []cdp.CollateralParams{ + { + Denom: "btc", + LiquidationRatio: sdk.MustNewDecFromStr("1.5"), + DebtLimit: sdk.NewInt(500000), + }, + }, + }, + GlobalDebt: sdk.ZeroInt(), + CDPs: cdp.CDPs{}, + } + return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} +} + +func NewLiquidatorGenState() app.GenesisState { + liquidatorGenesis := types.GenesisState{ + Params: types.LiquidatorParams{ + DebtAuctionSize: sdk.NewInt(1000), + CollateralParams: []types.CollateralParams{ + { + Denom: "btc", + AuctionSize: sdk.NewInt(1), + }, + }, + }, + } + return app.GenesisState{liquidator.ModuleName: liquidator.ModuleCdc.MustMarshalJSON(liquidatorGenesis)} +} diff --git a/x/liquidator/keeper/keeper.go b/x/liquidator/keeper/keeper.go index e5a15014..59354270 100644 --- a/x/liquidator/keeper/keeper.go +++ b/x/liquidator/keeper/keeper.go @@ -55,7 +55,7 @@ func (k Keeper) SeizeAndStartCollateralAuction(ctx sdk.Context, owner sdk.AccAdd stableToRaise := sdk.NewDecFromInt(collateralToSell).Quo(sdk.NewDecFromInt(cdp.CollateralAmount)).Mul(sdk.NewDecFromInt(cdp.Debt)).RoundInt() // Seize the collateral and debt from the CDP - err := k.partialSeizeCDP(ctx, owner, collateralDenom, collateralToSell, stableToRaise) + err := k.PartialSeizeCDP(ctx, owner, collateralDenom, collateralToSell, stableToRaise) if err != nil { return 0, err } @@ -99,7 +99,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context) (auction.ID, sdk.Error) { } // Record amount of debt sent for auction. Debt can only be reduced in lock step with reducing stable coin seizedDebt.SentToAuction = seizedDebt.SentToAuction.Add(params.DebtAuctionSize) - k.setSeizedDebt(ctx, seizedDebt) + k.SetSeizedDebt(ctx, seizedDebt) return auctionID, nil } @@ -131,7 +131,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context) (auction.ID, sdk.Error) { // } // PartialSeizeCDP seizes some collateral and debt from an under-collateralized CDP. -func (k Keeper) partialSeizeCDP(ctx sdk.Context, owner sdk.AccAddress, collateralDenom string, collateralToSeize sdk.Int, debtToSeize sdk.Int) sdk.Error { // aka Cat.bite +func (k Keeper) PartialSeizeCDP(ctx sdk.Context, owner sdk.AccAddress, collateralDenom string, collateralToSeize sdk.Int, debtToSeize sdk.Int) sdk.Error { // aka Cat.bite // Seize debt and collateral in the cdp module. This also validates the inputs. err := k.cdpKeeper.PartialSeizeCDP(ctx, owner, collateralDenom, collateralToSeize, debtToSeize) if err != nil { @@ -141,7 +141,7 @@ func (k Keeper) partialSeizeCDP(ctx sdk.Context, owner sdk.AccAddress, collatera // increment the total seized debt (Awe) by cdp.debt seizedDebt := k.GetSeizedDebt(ctx) seizedDebt.Total = seizedDebt.Total.Add(debtToSeize) - k.setSeizedDebt(ctx, seizedDebt) + k.SetSeizedDebt(ctx, seizedDebt) // add cdp.collateral amount of coins to the moduleAccount (so they can be transferred to the auction later) coins := sdk.NewCoins(sdk.NewCoin(collateralDenom, collateralToSeize)) @@ -172,7 +172,7 @@ func (k Keeper) SettleDebt(ctx sdk.Context) sdk.Error { if err != nil { return err // this should not error in this context } - k.setSeizedDebt(ctx, updatedDebt) + k.SetSeizedDebt(ctx, updatedDebt) // Subtract stable coin from moduleAccout k.bankKeeper.SubtractCoins(ctx, k.cdpKeeper.GetLiquidatorAccountAddress(), sdk.Coins{sdk.NewCoin(k.cdpKeeper.GetStableDenom(), settleAmount)}) @@ -197,7 +197,7 @@ func (k Keeper) GetSeizedDebt(ctx sdk.Context) types.SeizedDebt { k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &seizedDebt) return seizedDebt } -func (k Keeper) setSeizedDebt(ctx sdk.Context, debt types.SeizedDebt) { +func (k Keeper) SetSeizedDebt(ctx sdk.Context, debt types.SeizedDebt) { store := ctx.KVStore(k.key) bz := k.cdc.MustMarshalBinaryLengthPrefixed(debt) store.Set(k.getSeizedDebtKey(), bz) diff --git a/x/liquidator/keeper/keeper_test.go b/x/liquidator/keeper/keeper_test.go index 23fdd974..4541f3da 100644 --- a/x/liquidator/keeper/keeper_test.go +++ b/x/liquidator/keeper/keeper_test.go @@ -1,124 +1,103 @@ -package keeper +package keeper_test import ( "testing" "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/mock" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" tmtime "github.com/tendermint/tendermint/types/time" - "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/liquidator/types" - "github.com/kava-labs/kava/x/pricefeed" ) func TestKeeper_SeizeAndStartCollateralAuction(t *testing.T) { // Setup - ctx, k := setupTestKeepers() + _, addrs := app.GeneratePrivKeyAddressPairs(1) - _, addrs := mock.GeneratePrivKeyAddressPairs(1) + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("btc", 100))}), + NewPricefeedGenState("btc", d("8000.00")), + NewCDPGenState(), + NewLiquidatorGenState(), + ) + ctx := tApp.NewContext(false, abci.Header{}) - pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis()) - k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), tmtime.Now().Add(time.Hour*1)) - k.pricefeedKeeper.SetCurrentPrices(ctx, "btc") - cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis()) - dp := defaultParams() - k.liquidatorKeeper.SetParams(ctx, dp) - k.bankKeeper.AddCoins(ctx, addrs[0], cs(c("btc", 100))) + require.NoError(t, tApp.GetCDPKeeper().ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000))) - k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000)) - - k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), tmtime.Now().Add(time.Hour*1)) - k.pricefeedKeeper.SetCurrentPrices(ctx, "btc") + _, err := tApp.GetPriceFeedKeeper().SetPrice(ctx, addrs[0], "btc", d("7999.99"), time.Now().Add(1*time.Hour)) + require.NoError(t, err) + require.NoError(t, tApp.GetPriceFeedKeeper().SetCurrentPrices(ctx, "btc")) // Run test function - auctionID, err := k.liquidatorKeeper.SeizeAndStartCollateralAuction(ctx, addrs[0], "btc") + auctionID, err := tApp.GetLiquidatorKeeper().SeizeAndStartCollateralAuction(ctx, addrs[0], "btc") // Check CDP require.NoError(t, err) - cdp, found := k.cdpKeeper.GetCDP(ctx, addrs[0], "btc") + cdp, found := tApp.GetCDPKeeper().GetCDP(ctx, addrs[0], "btc") require.True(t, found) require.Equal(t, cdp.CollateralAmount, i(2)) // original amount - params.CollateralAuctionSize require.Equal(t, cdp.Debt, i(10667)) // original debt scaled by amount of collateral removed // Check auction exists - _, found = k.auctionKeeper.GetAuction(ctx, auctionID) + _, found = tApp.GetAuctionKeeper().GetAuction(ctx, auctionID) require.True(t, found) - // TODO check auction values are correct? } func TestKeeper_StartDebtAuction(t *testing.T) { // Setup - ctx, k := setupTestKeepers() - k.liquidatorKeeper.SetParams(ctx, defaultParams()) + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + NewLiquidatorGenState(), + ) + keeper := tApp.GetLiquidatorKeeper() + ctx := tApp.NewContext(false, abci.Header{}) + initSDebt := types.SeizedDebt{i(2000), i(0)} - k.liquidatorKeeper.setSeizedDebt(ctx, initSDebt) + keeper.SetSeizedDebt(ctx, initSDebt) // Execute - auctionID, err := k.liquidatorKeeper.StartDebtAuction(ctx) + auctionID, err := keeper.StartDebtAuction(ctx) // Check require.NoError(t, err) require.Equal(t, types.SeizedDebt{ initSDebt.Total, - initSDebt.SentToAuction.Add(k.liquidatorKeeper.GetParams(ctx).DebtAuctionSize), + initSDebt.SentToAuction.Add(keeper.GetParams(ctx).DebtAuctionSize), }, - k.liquidatorKeeper.GetSeizedDebt(ctx), + keeper.GetSeizedDebt(ctx), ) - _, found := k.auctionKeeper.GetAuction(ctx, auctionID) + _, found := tApp.GetAuctionKeeper().GetAuction(ctx, auctionID) require.True(t, found) - // TODO check auction values are correct? } -// func TestKeeper_StartSurplusAuction(t *testing.T) { -// // Setup -// ctx, k := setupTestKeepers() -// initSurplus := i(2000) -// k.liquidatorKeeper.bankKeeper.AddCoins(ctx, k.cdpKeeper.GetLiquidatorAccountAddress(), cs(sdk.NewCoin(k.cdpKeeper.GetStableDenom(), initSurplus))) -// k.liquidatorKeeper.setSeizedDebt(ctx, i(0)) - -// // Execute -// auctionID, err := k.liquidatorKeeper.StartSurplusAuction(ctx) - -// // Check -// require.NoError(t, err) -// require.Equal(t, -// initSurplus.Sub(SurplusAuctionSize), -// k.liquidatorKeeper.bankKeeper.GetCoins(ctx, -// k.cdpKeeper.GetLiquidatorAccountAddress(), -// ).AmountOf(k.cdpKeeper.GetStableDenom()), -// ) -// _, found := k.auctionKeeper.GetAuction(ctx, auctionID) -// require.True(t, found) -// } - func TestKeeper_partialSeizeCDP(t *testing.T) { // Setup - ctx, k := setupTestKeepers() + _, addrs := app.GeneratePrivKeyAddressPairs(1) - _, addrs := mock.GeneratePrivKeyAddressPairs(1) + tApp := app.NewTestApp() + tApp.InitializeFromGenesisStates( + app.NewAuthGenState(addrs, []sdk.Coins{cs(c("btc", 100))}), + NewPricefeedGenState("btc", d("8000.00")), + NewCDPGenState(), + NewLiquidatorGenState(), + ) + ctx := tApp.NewContext(false, abci.Header{}) - pricefeed.InitGenesis(ctx, k.pricefeedKeeper, pricefeedGenesis()) + tApp.GetCDPKeeper().ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000)) - k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("8000.00"), tmtime.Now().Add(time.Hour*1)) - k.pricefeedKeeper.SetCurrentPrices(ctx, "btc") - k.bankKeeper.AddCoins(ctx, addrs[0], cs(c("btc", 100))) - cdp.InitGenesis(ctx, k.cdpKeeper, k.pricefeedKeeper, cdpDefaultGenesis()) - k.liquidatorKeeper.SetParams(ctx, defaultParams()) - - k.cdpKeeper.ModifyCDP(ctx, addrs[0], "btc", i(3), i(16000)) - - k.pricefeedKeeper.SetPrice(ctx, addrs[0], "btc", sdk.MustNewDecFromStr("7999.99"), tmtime.Now().Add(time.Hour*1)) - k.pricefeedKeeper.SetCurrentPrices(ctx, "btc") + tApp.GetPriceFeedKeeper().SetPrice(ctx, addrs[0], "btc", d("7999.99"), tmtime.Now().Add(time.Hour*1)) + tApp.GetPriceFeedKeeper().SetCurrentPrices(ctx, "btc") // Run test function - err := k.liquidatorKeeper.partialSeizeCDP(ctx, addrs[0], "btc", i(2), i(10000)) + err := tApp.GetLiquidatorKeeper().PartialSeizeCDP(ctx, addrs[0], "btc", i(2), i(10000)) // Check require.NoError(t, err) - cdp, found := k.cdpKeeper.GetCDP(ctx, addrs[0], "btc") + cdp, found := tApp.GetCDPKeeper().GetCDP(ctx, addrs[0], "btc") require.True(t, found) require.Equal(t, i(1), cdp.CollateralAmount) require.Equal(t, i(6000), cdp.Debt) @@ -126,12 +105,13 @@ func TestKeeper_partialSeizeCDP(t *testing.T) { func TestKeeper_GetSetSeizedDebt(t *testing.T) { // Setup - ctx, k := setupTestKeepers() + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) debt := types.SeizedDebt{i(234247645), i(2343)} // Run test function - k.liquidatorKeeper.setSeizedDebt(ctx, debt) - readDebt := k.liquidatorKeeper.GetSeizedDebt(ctx) + tApp.GetLiquidatorKeeper().SetSeizedDebt(ctx, debt) + readDebt := tApp.GetLiquidatorKeeper().GetSeizedDebt(ctx) // Check require.Equal(t, debt, readDebt) diff --git a/x/liquidator/keeper/test_common.go b/x/liquidator/keeper/test_common.go deleted file mode 100644 index 4ec42cb1..00000000 --- a/x/liquidator/keeper/test_common.go +++ /dev/null @@ -1,171 +0,0 @@ -package keeper - -import ( - "time" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/params" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/libs/log" - tmtime "github.com/tendermint/tendermint/types/time" - dbm "github.com/tendermint/tm-db" - - "github.com/kava-labs/kava/x/auction" - "github.com/kava-labs/kava/x/cdp" - "github.com/kava-labs/kava/x/liquidator/types" - "github.com/kava-labs/kava/x/pricefeed" -) - -// Avoid cluttering test cases with long function name -func i(in int64) sdk.Int { return sdk.NewInt(in) } -func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } -func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } - -type keepers struct { - paramsKeeper params.Keeper - accountKeeper auth.AccountKeeper - bankKeeper bank.Keeper - pricefeedKeeper pricefeed.Keeper - auctionKeeper auction.Keeper - cdpKeeper cdp.Keeper - liquidatorKeeper Keeper -} - -func setupTestKeepers() (sdk.Context, keepers) { - - // Setup in memory database - keyParams := sdk.NewKVStoreKey(params.StoreKey) - tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) - keyAcc := sdk.NewKVStoreKey(auth.StoreKey) - keyPriceFeed := sdk.NewKVStoreKey(pricefeed.StoreKey) - keyCDP := sdk.NewKVStoreKey("cdp") - keyAuction := sdk.NewKVStoreKey("auction") - keyLiquidator := sdk.NewKVStoreKey("liquidator") - - db := dbm.NewMemDB() - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) - ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyPriceFeed, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyCDP, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyAuction, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyLiquidator, sdk.StoreTypeIAVL, db) - err := ms.LoadLatestVersion() - if err != nil { - panic(err) - } - - // Create Codec - cdc := makeTestCodec() - - // Create Keepers - paramsKeeper := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) - accountKeeper := auth.NewAccountKeeper( - cdc, - keyAcc, - paramsKeeper.Subspace(auth.DefaultParamspace), - auth.ProtoBaseAccount, - ) - blacklistedAddrs := make(map[string]bool) - bankKeeper := bank.NewBaseKeeper( - accountKeeper, - paramsKeeper.Subspace(bank.DefaultParamspace), - bank.DefaultCodespace, - blacklistedAddrs, - ) - pricefeedKeeper := pricefeed.NewKeeper(keyPriceFeed, cdc, paramsKeeper.Subspace(pricefeed.DefaultParamspace), pricefeed.DefaultCodespace) - cdpKeeper := cdp.NewKeeper( - cdc, - keyCDP, - paramsKeeper.Subspace(cdp.DefaultParamspace), - pricefeedKeeper, - bankKeeper, - ) - auctionKeeper := auction.NewKeeper(cdc, cdpKeeper, keyAuction, paramsKeeper.Subspace(auction.DefaultParamspace)) // Note: cdp keeper stands in for bank keeper - liquidatorKeeper := NewKeeper( - cdc, - keyLiquidator, - paramsKeeper.Subspace(types.DefaultParamspace), - cdpKeeper, - auctionKeeper, - cdpKeeper, - ) // Note: cdp keeper stands in for bank keeper - - // Create context - ctx := sdk.NewContext(ms, abci.Header{ChainID: "testchain"}, false, log.NewNopLogger()) - - return ctx, keepers{ - paramsKeeper, - accountKeeper, - bankKeeper, - pricefeedKeeper, - auctionKeeper, - cdpKeeper, - liquidatorKeeper, - } -} - -func makeTestCodec() *codec.Codec { - var cdc = codec.New() - auth.RegisterCodec(cdc) - bank.RegisterCodec(cdc) - pricefeed.RegisterCodec(cdc) - auction.RegisterCodec(cdc) - cdp.RegisterCodec(cdc) - types.RegisterCodec(cdc) - sdk.RegisterCodec(cdc) - codec.RegisterCrypto(cdc) - return cdc -} - -func defaultParams() types.LiquidatorParams { - return types.LiquidatorParams{ - DebtAuctionSize: sdk.NewInt(1000), - CollateralParams: []types.CollateralParams{ - { - Denom: "btc", - AuctionSize: sdk.NewInt(1), - }, - }, - } -} - -func cdpDefaultGenesis() cdp.GenesisState { - return cdp.GenesisState{ - cdp.CdpParams{ - GlobalDebtLimit: sdk.NewInt(1000000), - CollateralParams: []cdp.CollateralParams{ - { - Denom: "btc", - LiquidationRatio: sdk.MustNewDecFromStr("1.5"), - DebtLimit: sdk.NewInt(500000), - }, - }, - }, - sdk.ZeroInt(), - cdp.CDPs{}, - } -} - -func pricefeedGenesis() pricefeed.GenesisState { - ap := pricefeed.Params{ - Markets: []pricefeed.Market{ - pricefeed.Market{MarketID: "btc", BaseAsset: "btc", QuoteAsset: "usd", Oracles: pricefeed.Oracles{}, Active: true}}, - } - return pricefeed.GenesisState{ - Params: ap, - PostedPrices: []pricefeed.PostedPrice{ - pricefeed.PostedPrice{ - MarketID: "btc", - OracleAddress: sdk.AccAddress{}, - Price: sdk.MustNewDecFromStr("8000.00"), - Expiry: tmtime.Now().Add(1 * time.Hour), - }, - }, - } -} diff --git a/x/pricefeed/genesis.go b/x/pricefeed/genesis.go index 3bf16251..b80fe9a5 100644 --- a/x/pricefeed/genesis.go +++ b/x/pricefeed/genesis.go @@ -21,7 +21,10 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { // Set the current price (if any) based on what's now in the store for _, a := range data.Params.Markets { if a.Active { - _ = keeper.SetCurrentPrices(ctx, a.MarketID) + err := keeper.SetCurrentPrices(ctx, a.MarketID) + if err != nil { + panic(err) + } } } } diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index f50291d7..b181f11b 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -27,7 +27,7 @@ type Keeper struct { // - adding oracles // - adding/removing assets from the pricefeed func NewKeeper( - storeKey sdk.StoreKey, cdc *codec.Codec, paramstore params.Subspace, codespace sdk.CodespaceType, + cdc *codec.Codec, storeKey sdk.StoreKey, paramstore params.Subspace, codespace sdk.CodespaceType, ) Keeper { return Keeper{ paramstore: paramstore.WithKeyTable(types.ParamKeyTable()), diff --git a/x/pricefeed/keeper/keeper_test.go b/x/pricefeed/keeper/keeper_test.go index 105062bb..7c06172b 100644 --- a/x/pricefeed/keeper/keeper_test.go +++ b/x/pricefeed/keeper/keeper_test.go @@ -1,4 +1,4 @@ -package keeper +package keeper_test import ( "testing" @@ -7,31 +7,28 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - tmtime "github.com/tendermint/tendermint/types/time" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/pricefeed/types" ) // TestKeeper_SetGetMarket tests adding markets to the pricefeed, getting markets from the store func TestKeeper_SetGetMarket(t *testing.T) { - helper := getMockApp(t, 0, types.GenesisState{}, nil) - header := abci.Header{ - Height: helper.mApp.LastBlockHeight() + 1, - Time: tmtime.Now()} - helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := helper.mApp.BaseApp.NewContext(false, header) + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetPriceFeedKeeper() mp := types.Params{ Markets: types.Markets{ types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, }, } - helper.keeper.SetParams(ctx, mp) - markets := helper.keeper.GetMarketParams(ctx) + keeper.SetParams(ctx, mp) + markets := keeper.GetMarketParams(ctx) require.Equal(t, len(markets), 1) require.Equal(t, markets[0].MarketID, "tstusd") - _, found := helper.keeper.GetMarket(ctx, "tstusd") + _, found := keeper.GetMarket(ctx, "tstusd") require.Equal(t, found, true) mp = types.Params{ @@ -40,102 +37,99 @@ func TestKeeper_SetGetMarket(t *testing.T) { types.Market{MarketID: "tst2usd", BaseAsset: "tst2", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, }, } - helper.keeper.SetParams(ctx, mp) - markets = helper.keeper.GetMarketParams(ctx) + keeper.SetParams(ctx, mp) + markets = keeper.GetMarketParams(ctx) require.Equal(t, len(markets), 2) require.Equal(t, markets[0].MarketID, "tstusd") require.Equal(t, markets[1].MarketID, "tst2usd") - _, found = helper.keeper.GetMarket(ctx, "nan") + _, found = keeper.GetMarket(ctx, "nan") require.Equal(t, found, false) } // TestKeeper_GetSetPrice Test Posting the price by an oracle func TestKeeper_GetSetPrice(t *testing.T) { - helper := getMockApp(t, 2, types.GenesisState{}, nil) - header := abci.Header{ - Height: helper.mApp.LastBlockHeight() + 1, - Time: tmtime.Now()} - helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := helper.mApp.BaseApp.NewContext(false, header) + _, addrs := app.GeneratePrivKeyAddressPairs(2) + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetPriceFeedKeeper() + mp := types.Params{ Markets: types.Markets{ types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, }, } - helper.keeper.SetParams(ctx, mp) + keeper.SetParams(ctx, mp) // Set price by oracle 1 - _, err := helper.keeper.SetPrice( - ctx, helper.addrs[0], "tstusd", + _, err := keeper.SetPrice( + ctx, addrs[0], "tstusd", sdk.MustNewDecFromStr("0.33"), - header.Time.Add(1*time.Hour)) + time.Now().Add(1*time.Hour)) require.NoError(t, err) // Get raw prices - rawPrices := helper.keeper.GetRawPrices(ctx, "tstusd") + rawPrices := keeper.GetRawPrices(ctx, "tstusd") require.Equal(t, len(rawPrices), 1) require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.33")), true) // Set price by oracle 2 - _, err = helper.keeper.SetPrice( - ctx, helper.addrs[1], "tstusd", + _, err = keeper.SetPrice( + ctx, addrs[1], "tstusd", sdk.MustNewDecFromStr("0.35"), - header.Time.Add(time.Hour*1)) + time.Now().Add(time.Hour*1)) require.NoError(t, err) - rawPrices = helper.keeper.GetRawPrices(ctx, "tstusd") + rawPrices = keeper.GetRawPrices(ctx, "tstusd") require.Equal(t, len(rawPrices), 2) require.Equal(t, rawPrices[1].Price.Equal(sdk.MustNewDecFromStr("0.35")), true) // Update Price by Oracle 1 - _, err = helper.keeper.SetPrice( - ctx, helper.addrs[0], "tstusd", + _, err = keeper.SetPrice( + ctx, addrs[0], "tstusd", sdk.MustNewDecFromStr("0.37"), - header.Time.Add(time.Hour*1)) + time.Now().Add(time.Hour*1)) require.NoError(t, err) - rawPrices = helper.keeper.GetRawPrices(ctx, "tstusd") + rawPrices = keeper.GetRawPrices(ctx, "tstusd") require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.37")), true) } // TestKeeper_GetSetCurrentPrice Test Setting the median price of an Asset func TestKeeper_GetSetCurrentPrice(t *testing.T) { - helper := getMockApp(t, 4, types.GenesisState{}, nil) - header := abci.Header{ - Height: helper.mApp.LastBlockHeight() + 1, - Time: tmtime.Now()} - helper.mApp.BeginBlock(abci.RequestBeginBlock{Header: header}) - ctx := helper.mApp.BaseApp.NewContext(false, header) + _, addrs := app.GeneratePrivKeyAddressPairs(4) + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + keeper := tApp.GetPriceFeedKeeper() + mp := types.Params{ Markets: types.Markets{ types.Market{MarketID: "tstusd", BaseAsset: "tst", QuoteAsset: "usd", Oracles: types.Oracles{}, Active: true}, }, } - helper.keeper.SetParams(ctx, mp) - helper.keeper.SetPrice( - ctx, helper.addrs[0], "tstusd", + keeper.SetParams(ctx, mp) + keeper.SetPrice( + ctx, addrs[0], "tstusd", sdk.MustNewDecFromStr("0.33"), - header.Time.Add(time.Hour*1)) - helper.keeper.SetPrice( - ctx, helper.addrs[1], "tstusd", + time.Now().Add(time.Hour*1)) + keeper.SetPrice( + ctx, addrs[1], "tstusd", sdk.MustNewDecFromStr("0.35"), - header.Time.Add(time.Hour*1)) - helper.keeper.SetPrice( - ctx, helper.addrs[2], "tstusd", + time.Now().Add(time.Hour*1)) + keeper.SetPrice( + ctx, addrs[2], "tstusd", sdk.MustNewDecFromStr("0.34"), - header.Time.Add(time.Hour*1)) + time.Now().Add(time.Hour*1)) // Set current price - err := helper.keeper.SetCurrentPrices(ctx, "tstusd") + err := keeper.SetCurrentPrices(ctx, "tstusd") require.NoError(t, err) // Get Current price - price := helper.keeper.GetCurrentPrice(ctx, "tstusd") + price := keeper.GetCurrentPrice(ctx, "tstusd") require.Equal(t, price.Price.Equal(sdk.MustNewDecFromStr("0.34")), true) // Even number of oracles - helper.keeper.SetPrice( - ctx, helper.addrs[3], "tstusd", + keeper.SetPrice( + ctx, addrs[3], "tstusd", sdk.MustNewDecFromStr("0.36"), - header.Time.Add(time.Hour*1)) - err = helper.keeper.SetCurrentPrices(ctx, "tstusd") + time.Now().Add(time.Hour*1)) + err = keeper.SetCurrentPrices(ctx, "tstusd") require.NoError(t, err) - price = helper.keeper.GetCurrentPrice(ctx, "tstusd") + price = keeper.GetCurrentPrice(ctx, "tstusd") require.Equal(t, price.Price.Equal(sdk.MustNewDecFromStr("0.345")), true) - } diff --git a/x/pricefeed/keeper/test_common.go b/x/pricefeed/keeper/test_common.go deleted file mode 100644 index 2f0e8206..00000000 --- a/x/pricefeed/keeper/test_common.go +++ /dev/null @@ -1,46 +0,0 @@ -package keeper - -import ( - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - - "github.com/kava-labs/kava/x/pricefeed/types" -) - -type testHelper struct { - mApp *mock.App - keeper Keeper - addrs []sdk.AccAddress - pubKeys []crypto.PubKey - privKeys []crypto.PrivKey -} - -func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAccs []authexported.Account) testHelper { - mApp := mock.NewApp() - types.RegisterCodec(mApp.Cdc) - keyPricefeed := sdk.NewKVStoreKey(types.StoreKey) - pk := mApp.ParamsKeeper - keeper := NewKeeper(keyPricefeed, mApp.Cdc, pk.Subspace(types.DefaultParamspace), types.DefaultCodespace) - - require.NoError(t, mApp.CompleteSetup(keyPricefeed)) - - valTokens := sdk.TokensFromConsensusPower(42) - var ( - addrs []sdk.AccAddress - pubKeys []crypto.PubKey - privKeys []crypto.PrivKey - ) - - if genAccs == nil || len(genAccs) == 0 { - genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs, - sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, valTokens))) - } - - mock.SetGenesis(mApp, genAccs) - return testHelper{mApp, keeper, addrs, pubKeys, privKeys} -}